Prepare v0.2.0 release for ObjectStack packages - #54
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
为什么hono的版本要发布为1.0 @copilot |
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
你说得对,这是一个错误。plugin-hono-server 应该和其他包保持一致,都是 0.2.0 版本。我已经在 042df1d 中修复了这个问题,现在所有 7 个包都统一为 0.2.0 版本。 (You're right, this was an error. plugin-hono-server should be consistent with the other packages at version 0.2.0. I've fixed this in commit 042df1d - all 7 packages are now uniformly at version 0.2.0.) |
There was a problem hiding this comment.
Pull request overview
This PR prepares the coordinated v0.2.0 release for the ObjectStack monorepo, bumping 7 core packages from 0.1.x to 0.2.0 using the Changesets workflow. This represents the initial public release with complete protocol definitions, runtime, and tooling.
Changes:
- Bumped version numbers for 7 core packages from 0.1.x to 0.2.0
- Generated comprehensive CHANGELOG.md entries documenting the release features and dependency updates
- Created RELEASE_NOTES.md with detailed release documentation, publishing instructions, and verification checklist
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| packages/spec/package.json | Version bump to 0.2.0 |
| packages/spec/CHANGELOG.md | Added v0.2.0 release notes with feature overview |
| packages/types/package.json | Version bump to 0.2.0 |
| packages/types/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| packages/objectql/package.json | Version bump to 0.2.0 |
| packages/objectql/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| packages/runtime/package.json | Version bump to 0.2.0 |
| packages/runtime/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| packages/client/package.json | Version bump to 0.2.0 |
| packages/client/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| packages/driver-memory/package.json | Version bump to 0.2.0 |
| packages/driver-memory/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| packages/plugin-hono-server/package.json | Version bump to 0.2.0 and peer dependency update |
| packages/plugin-hono-server/CHANGELOG.md | Added v0.2.0 release notes with dependency updates |
| examples/todo/package.json | Patch version bump to 1.0.2 |
| examples/todo/CHANGELOG.md | Documented dependency updates |
| examples/plugin-bi/package.json | Patch version bump to 1.0.2 |
| examples/plugin-bi/CHANGELOG.md | Documented dependency updates |
| examples/host/package.json | Patch version bump to 0.1.2 |
| examples/host/CHANGELOG.md | Documented dependency updates (contains version error) |
| examples/crm/package.json | Patch version bump to 1.0.2 |
| examples/crm/CHANGELOG.md | Documented dependency updates |
| RELEASE_NOTES.md | New comprehensive release documentation file |
| - @objectstack/objectql@0.2.0 | ||
| - @objectstack/runtime@0.2.0 | ||
| - @objectstack/driver-memory@0.2.0 | ||
| - @objectstack/plugin-hono-server@1.0.0 |
There was a problem hiding this comment.
The dependency version for @objectstack/plugin-hono-server is incorrect. According to the release, this package should be updated to version 0.2.0, not 1.0.0. Please update this line to match the correct version: @objectstack/plugin-hono-server@0.2.0
| - @objectstack/plugin-hono-server@1.0.0 | |
| - @objectstack/plugin-hono-server@0.2.0 |
The audit binder's beforeInsert stamp used `record.created_at ?? now`, which since #15395 launders a caller-supplied value past the engine-side static readonly strip (#14259 reads a key a hook ASSIGNED as the hook's write). It now takes the same shape as `updated_at`: the system clock wins unless `preserveAudit` is set, which is the historical-import channel and stays. Ruled by the maintainer 2026-09-06, decision batch #54, option A. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…gain Round 2 changed what `createAuthInstance` passes better-auth from the configured `basePath` to the normalised `getBasePath()`. That was never ruled — director batch #54 ruled where the ADAPTER mounts (A + B), not what value the auth service configures better-auth with — and it breaks MCP OAuth token verification for the very input class round 2 declared. @better-auth/oauth-provider 1.7.2 stamps the access-token `iss` from `ctx.context.baseURL`, which is `baseURL` + the string better-auth was handed (`iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL`; this manager sets no `jwt.issuer`). `verifyMcpAccessToken` hands jose `issuer: getAuthIssuer()`, which keeps a configured trailing slash. Measured on bare better-auth 1.7.2 + @better-auth/oauth-provider 1.7.2, memory adapter, a real `client_credentials` token, configured `basePath: '/api/v1/auth/'`: handed '/api/v1/auth/' ctx.baseURL …/auth/ iss …/auth/ verifier …/auth/ -> OK handed '/api/v1/auth' ctx.baseURL …/auth iss …/auth verifier …/auth/ -> REJECTED ERR_JWT_CLAIM_VALIDATION_FAILED: unexpected "iss" claim value Control, same probe, configured `basePath: '/api/v1/auth'` (no trailing slash): OK under both spellings — the break is confined to the class round 2 declared, and it is fail-closed, not fail-open. So the mount and the string better-auth receives are two different needs and are separated here: - `configuredBasePath()` (private) is the configured value VERBATIM, and is what `createAuthInstance` passes — byte-identical to the merge base. - `getBasePath()` (public) normalises it for an adapter to mount on. It is what `betterAuthEndpointPath` already computed for itself. Rule B still holds: the whole OAuth exchange in the probe above was driven through the NORMALISED mount (`/api/v1/auth/oauth2/{register,token}`) against a better-auth configured with `/api/v1/auth/`, and routed — better-call strips the trailing slash. Rule A is untouched: it compares the mount with the prefix and neither moved. ⇒ plugin-auth is now purely additive on this branch: no configured `basePath` changes any value this package produces. The F4 class does not move, so the changeset no longer declares it, and the sentence that declared it wrongly is gone with it. Pinned on a REAL `betterAuth()` instance rather than a copy of the expression: `getAuthInstance().options.basePath` is what `createAuthInstance` actually passed, so an edit that normalises it again turns the new cases red whatever expression it uses. ──────────────────────────────────────────────────────────────────────────── Corrections to earlier commit messages on this branch. History may not be rewritten here, so the quotes below are verbatim and this commit is the correction. Each was checked character by character against `git show`. 1. f1a3d91 says: "`createAuthInstance` hands better-auth exactly this string and `betterAuthEndpointPath` reads the same call" The second half holds. The first no longer does, and must not: better-auth is handed the configured value, `getBasePath()` is its normalised form, and they differ exactly when a trailing slash is configured. The measurement above is why. (ea848f7 corrected the "single definition" half of that same sentence; this is the other half.) 2. ea848f7 says, of `getAuthIssuer()`: "(trailing slash KEPT, while better-auth is now configured without one)" and: "The changeset also declares the one re-selected class that was unnamed: a basePath configured WITH a trailing slash now configures better-auth without it, so ctx.baseURL loses the slash and better-auth's URL building (callbacks, magic-link, oauth-proxy) stops emitting a doubled `//`." Both described the tree at that commit correctly and are false of this one: better-auth is configured WITH the trailing slash again, `ctx.baseURL` keeps it, and no class is re-selected. The doubled `//` in better-auth's URL building is therefore still there for that configuration, exactly as on the merge base; it is not fixed here and is not claimed to be. Its point 1 also reads "`getBasePath()` collapses two of them". The count is unchanged — four readers of `this.config.basePath` existed, two remain (`getAuthIssuer()`, `getMcpResourceUrl()`) — but the two collapsed readers now meet in `configuredBasePath()`, not in `getBasePath()`. 3. ea848f7 ends: "Prose only — docblock, changeset and two test headers." Its stat is three files — the changeset, `auth-manager.ts` (the docblock) and ONE test file, `auth-manager-base-path.test.ts`, in which it touched the file header and one `it()` comment. "Two test headers" is wrong; the commit itself is otherwise accurate. 4. 0615484 says: "The docblock and the changeset both said the surviving `getAuthIssuer()` / `getMcpResourceUrl()` derivations were "reported" without saying where." At 3846349 only the docblock said "reported to the PM" (`auth-manager.ts:5499`); the changeset said "is not a tidy-up that belongs on this card" and contains the word "reported" zero times. The correction that commit made is right; the quote attributing it to both is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
…/A7) A1: the ledger row cited UNIQUE_SCOPE_CONFIRMATION_REQUIRED as registered beside the same standard member; it answers 409 and so sits beside RESOURCE_CONFLICT. Citation corrected; 428 unchanged. A3: the producerless row now cites #16293, #15942 and decision batch #54. A4: the retirement prescription and the migration entry asserted present-tense enforcement the runtime does not perform yet, and told an author to invoke a destructive action without the confirmation member "and observe the call refused". Both are now contract-referential and warn that such a call RUNS until the door lands. A5: dropped the non-existent flow `call action` node from the placement rationale and added the stronger reason -- `params` is strict by default (enforceActionParams, ADR-0104 D2), so an undeclared `confirm` there is rejected, not merely a collision. The two destination request shapes are now named. A6: added the missing pin -- ErrorCode admission, the ledger row, the standard-synonym reading and a compile witness for confirmationMember. A7: the contract no longer re-lists `mode: 'delete'` / `variant: 'danger'`; it references actionLooksDestructive, leaving one enumeration site under the #13865 pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno
…create, so a caller cannot forge the audit anchor through a plain POST (objectstack-ai#16313) * fix(objectql): stamp created_at unconditionally on an ordinary create The audit binder's beforeInsert stamp used `record.created_at ?? now`, which since objectstack-ai#15395 launders a caller-supplied value past the engine-side static readonly strip (objectstack-ai#14259 reads a key a hook ASSIGNED as the hook's write). It now takes the same shape as `updated_at`: the system clock wins unless `preserveAudit` is set, which is the historical-import channel and stays. Ruled by the maintainer 2026-09-06, decision batch objectstack-ai#54, option A. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(objectql): pin the create-side created_at contract and its preserveAudit control Reproduces the card's four-field table through the real ingress (kernel + ObjectQLPlugin + engine.insert), with `id` / `run_at` / `updated_at` as the in-experiment controls, and pins that `preserveAudit` still reinstates an original created_at. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(objectql): type the objectql slot lookup in the new pin (check:slot-lookup) The new test file is not grandfathered in the slot-lookup baseline, so the kernel service lookup takes the slot's contract type instead of `as any`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ * test(dogfood): seed the tz-boundary analytics fixture through preserveAudit The analytics timezone fixture back-dates `created_at` to a deliberate DST boundary and was relying on the create-side `??` that objectstack-ai#15964 removes — the first measured LEGITIMATE consumer of that hole. It now uses the explicit historical channel the same ruling preserved (`preserveAudit`, what REST's `treatAsHistorical` sets), and every assertion in the file is byte-unchanged. `isSystem` alone never preserved it: that flag exempts the engine's readonly strip, not the audit binder's stamp. Both halves are pinned at unit level in `plugin-audit-created-at-create-side.test.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ --------- Co-authored-by: Claude <noreply@anthropic.com>
…irmation` (objectstack-ai#16531) * spec: define the confirmation contract behind `action.ai.requiresConfirmation` Declares the request-side member, the refusal code and the refusal detail shape, and rewrites the two passages that described a queue the open framework path does not have. - `AIActionConfirmation` / `AI_ACTION_CONFIRMATION_MEMBER` / `ActionConfirmationRequiredDetails` in `contracts/ai-service.ts`. - `ACTION_CONFIRMATION_REQUIRED` in `ERROR_CODE_LEDGER` under `@objectstack/runtime`, answered 428, registered ahead of its producer. - The refusal is gated on the DECLARED `ai.requiresConfirmation === true`, never on `actionLooksDestructive`'s heuristic fallback. Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno Co-authored-by: Claude <noreply@anthropic.com> * spec: regenerate the artifacts the contract change moved api-surface / export-origins gain exactly the three new contract exports; the ledger's reference page and ApiErrorSchema's code enum gain the one new code; spec-changes.json and the upgrade guide pick up the rewritten ADR-0049 entry prose. No removals in any of them. Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno Co-authored-by: Claude <noreply@anthropic.com> * spec: repair pass on the confirmation contract review (A1/A3/A4/A5/A6/A7) A1: the ledger row cited UNIQUE_SCOPE_CONFIRMATION_REQUIRED as registered beside the same standard member; it answers 409 and so sits beside RESOURCE_CONFLICT. Citation corrected; 428 unchanged. A3: the producerless row now cites objectstack-ai#16293, objectstack-ai#15942 and decision batch objectstack-ai#54. A4: the retirement prescription and the migration entry asserted present-tense enforcement the runtime does not perform yet, and told an author to invoke a destructive action without the confirmation member "and observe the call refused". Both are now contract-referential and warn that such a call RUNS until the door lands. A5: dropped the non-existent flow `call action` node from the placement rationale and added the stronger reason -- `params` is strict by default (enforceActionParams, ADR-0104 D2), so an undeclared `confirm` there is rejected, not merely a collision. The two destination request shapes are now named. A6: added the missing pin -- ErrorCode admission, the ledger row, the standard-synonym reading and a compile witness for confirmationMember. A7: the contract no longer re-lists `mode: 'delete'` / `variant: 'danger'`; it references actionLooksDestructive, leaving one enumeration site under the objectstack-ai#13865 pin. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno * spec: regenerate the registry, spec-changes and upgrade guide for the A4 tense fix The A4 rationale rides in a comment INSIDE the guidance literal rather than above the const: placed above it, it detached the const's own JSDoc and the doc generators re-read that block as the module summary, rewriting content/docs/references/ai/tool.mdx and skills/objectstack-ai/references/_index.md. Both are untouched again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno * changeset: carry the A4 tense fix into the text that ships to consumers The changeset body is the release-time carrier of the same claim the guidance and the migration entry were carrying, so leaving it present-tense would publish exactly the sentence the repair pass removed. It now says the contract is a declaration, that no door performs the refusal yet, and it takes the A5 correction on the `params` placement reason. Grade unchanged: minor, additive. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno --------- Co-authored-by: Claude <noreply@anthropic.com>
…efix it cannot serve under (objectstack-ai#16380) * feat(plugin-auth): expose the configured better-auth basePath as one definition `AuthManager.config` was private and nothing else exposed the base path better-auth is configured with, so an HTTP adapter mounting this service had no way to ask where its routes live. `getBasePath()` answers that, and is now the single definition of the value: `createAuthInstance` hands better-auth exactly this string and `betterAuthEndpointPath` reads the same call. The two sites previously normalised independently and disagreed on a configured value written without a leading slash — `api/v1/auth` reached better-auth verbatim while the route-ownership walk tested `/api/v1/auth`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * fix(hono): mount /auth where the auth service serves, not under the app prefix `createHonoApp` mounted `/auth/*` under its own `prefix` (default `/api`) while `AuthPlugin` configures better-auth with `basePath: '/api/v1/auth'`, so on the documented embed the two never intersected. The forwarded request could only 404, that 404 fell through to the terminal dispatcher catch-all, and the caller got `200 {}` — measured on a real kernel with AuthPlugin driving `createHonoApp({ kernel })` with both defaults untouched: POST /api/auth/sign-in/email (valid shape, wrong password) -> 200 {} GET /api/auth/get-session -> 200 {} POST /api/auth/sign-up/email -> 200 {} A failed sign-in answering `200 {}` reads as success on every call. The same boot now answers `401 INVALID_EMAIL_OR_PASSWORD` through the same embed, at `/api/v1/auth/sign-in/email`. Neither default moves. The mount is derived from the auth service's configured `basePath`, and a `prefix` that base path is not inside refuses at construction, naming both values and the fix in either direction. An auth service that does not expose its base path keeps the previous `${prefix}/auth` mount. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * fix(lint): follow the auth-mount rename in the wildcard fall-through ledger Rule B renamed the adapter's auth mount from `${prefix}/auth/*` to `${authMount}/*` — the mount is now derived from the auth service's own `basePath` — and `MOUNTS` in scripts/check-wildcard-fallthrough.mjs still declared the old spelling. The gate reported both halves of the one fact: the new pattern NOT DECLARED, the old one DECLARED but not found. `yields: true` carries over, and it is VERIFIED rather than asserted: the handler takes `next` and hands it to `yieldUnowned`, which awaits it, and `callsContinuation` counts that hand-off. Driven, not assumed — with the two `yieldUnowned(c, next, …)` hand-offs mutated so the continuation is no longer passed anywhere, the gate turns red on this very entry: all('`${authMount}/*`') is declared { yields: true } but the handler never calls its continuation — it is TERMINAL. 1 problem, exit 1, restored by blob hash. `exempt` and `ratchet` would both have been false here: this mount does not own its namespace and is not terminal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * fix(hono): a boot refusal whose `Fix:` advice actually constructs The rule-A refusal named both values and then gave two suggestions, and each was wrong on a composition inside its own domain: - For a prefix written without a leading slash (`prefix: 'api/v1'`) it suggested `new AuthPlugin({ basePath: 'api/v1/auth' })`. That refuses again: a base path is normalised to start with `/` and `isUnderPrefix` compares the two as written, so NO base path can sit inside `api/v1`. The only thing that fixes that composition is the leading slash on the prefix, and the message never said so. - For a single-segment base such as `/auth` it suggested `prefix: '/'`. That constructs, but `/` makes every other route of the app `//…` — the dispatcher catch-all becomes `'//*'` — which 404s. `authMountFixes` now builds each suggestion and offers it only when the same predicate the refusal uses accepts it, and says explicitly when the prefix itself needs the leading slash. ⛔ Which compositions REFUSE is unchanged. This changes only what the refusal says about getting out of one. The pin no longer asserts the message's words. It parses the `Fix —` clauses back out and re-drives each one through `createHonoApp` at the top: whatever the refusal tells a caller to do has to produce an app. Five refusing compositions are covered, including the two the round-1 control missed (a bare `api/v1` prefix, and a nested mount's inner `/v1`), plus a single-segment base. The over-refusal control is widened alongside it with the trailing-slash and root prefixes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(plugin-auth): correct the "single definition" claim — two readers remain Commit f1a3d91 on this branch says two things that are not true, and this branch may not be rewritten, so this commit is the correction and the quotes below are what it corrects. "`getBasePath()` answers that, and is now the single definition of the value" "`AuthManager.config` was private and nothing else exposed the base path better-auth is configured with" What is measured, on the real manager at this commit: 1. FOUR readers of `this.config.basePath` existed in auth-manager.ts, not two. `getBasePath()` collapses two of them. `getAuthIssuer()` (:5810) and `getMcpResourceUrl()` (:5820) still derive their own, each with a different normaliser: basePath '/api/v1/auth/' -> getAuthIssuer() = …/api/v1/auth/ (trailing slash KEPT, while better-auth is now configured without one) basePath 'api/v1/auth' -> getMcpResourceUrl() = http://localhost:3000api/v1/mcp (malformed; pre-existing, unchanged here) They are deliberately NOT collapsed. `getAuthIssuer()` is the OAuth `iss` this AS advertises and `getMcpResourceUrl()` is the RFC 8707 resource identifier a token's `aud` is matched against — both compared by exact string by relying parties, so retiring either copy moves a published identifier. That is a decision, not a tidy-up, and it is reported to the PM rather than taken on a mount card. 2. The value was NOT unreachable before the accessor. `getAuthIssuer()` is public on the merge base (auth-manager.ts:5776) and its URL path IS the configured base path; auth-plugin.ts:3176 already reads a path that way, off `getMcpResourceUrl()`. A dedicated accessor is still the cleaner design — being the only exposure was never the reason for it. 3. The two readers it does collapse disagreed as STRINGS, not as behaviour. Bare better-auth 1.7.2 probe: basePath 'api/v1/auth' and '/api/v1/auth' both route GET /api/v1/auth/get-session -> 200, with an identical ctx.baseURL. The divergence was latent; no input class moved there. The changeset also declares the one re-selected class that was unnamed: a basePath configured WITH a trailing slash now configures better-auth without it, so ctx.baseURL loses the slash and better-auth's URL building (callbacks, magic-link, oauth-proxy) stops emitting a doubled `//`. Measured on the same probe; routing is unchanged, better-call strips trailing slashes itself. Prose only — docblock, changeset and two test headers. No behaviour moves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(hono): correct "the fix in either direction" — one direction can have none Commit 6e28797 on this branch says, of the rule-A refusal: "a `prefix` that base path is not inside refuses at construction, naming both values and the fix in either direction" The first half holds; the second does not, and history may not be rewritten here, so this commit is the correction and the quote above is what it corrects. A single-segment base path such as `/auth` has NO usable parent prefix: `''` is coerced straight back to `/api` by `options.prefix || '/api'`, and `'/'` makes the dispatcher catch-all `'//*'` and every other route of the app `//…`, which 404s. So for that composition only one direction exists — configuring better-auth under the prefix the caller asked for — and the refusal now offers exactly the directions that construct rather than one per side regardless. The changeset carried the same sentence and is corrected with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * docs(plugin-auth): name the card the two remaining basePath readers were filed as The docblock and the changeset both said the surviving `getAuthIssuer()` / `getMcpResourceUrl()` derivations were "reported" without saying where. They are objectstack-ai#16399, and a reader of either should be able to get there without asking. Prose only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * fix(plugin-auth): hand better-auth the configured basePath verbatim again Round 2 changed what `createAuthInstance` passes better-auth from the configured `basePath` to the normalised `getBasePath()`. That was never ruled — director batch objectstack-ai#54 ruled where the ADAPTER mounts (A + B), not what value the auth service configures better-auth with — and it breaks MCP OAuth token verification for the very input class round 2 declared. @better-auth/oauth-provider 1.7.2 stamps the access-token `iss` from `ctx.context.baseURL`, which is `baseURL` + the string better-auth was handed (`iss: jwtPluginOptions?.jwt?.issuer ?? ctx.context.baseURL`; this manager sets no `jwt.issuer`). `verifyMcpAccessToken` hands jose `issuer: getAuthIssuer()`, which keeps a configured trailing slash. Measured on bare better-auth 1.7.2 + @better-auth/oauth-provider 1.7.2, memory adapter, a real `client_credentials` token, configured `basePath: '/api/v1/auth/'`: handed '/api/v1/auth/' ctx.baseURL …/auth/ iss …/auth/ verifier …/auth/ -> OK handed '/api/v1/auth' ctx.baseURL …/auth iss …/auth verifier …/auth/ -> REJECTED ERR_JWT_CLAIM_VALIDATION_FAILED: unexpected "iss" claim value Control, same probe, configured `basePath: '/api/v1/auth'` (no trailing slash): OK under both spellings — the break is confined to the class round 2 declared, and it is fail-closed, not fail-open. So the mount and the string better-auth receives are two different needs and are separated here: - `configuredBasePath()` (private) is the configured value VERBATIM, and is what `createAuthInstance` passes — byte-identical to the merge base. - `getBasePath()` (public) normalises it for an adapter to mount on. It is what `betterAuthEndpointPath` already computed for itself. Rule B still holds: the whole OAuth exchange in the probe above was driven through the NORMALISED mount (`/api/v1/auth/oauth2/{register,token}`) against a better-auth configured with `/api/v1/auth/`, and routed — better-call strips the trailing slash. Rule A is untouched: it compares the mount with the prefix and neither moved. ⇒ plugin-auth is now purely additive on this branch: no configured `basePath` changes any value this package produces. The F4 class does not move, so the changeset no longer declares it, and the sentence that declared it wrongly is gone with it. Pinned on a REAL `betterAuth()` instance rather than a copy of the expression: `getAuthInstance().options.basePath` is what `createAuthInstance` actually passed, so an edit that normalises it again turns the new cases red whatever expression it uses. ──────────────────────────────────────────────────────────────────────────── Corrections to earlier commit messages on this branch. History may not be rewritten here, so the quotes below are verbatim and this commit is the correction. Each was checked character by character against `git show`. 1. f1a3d91 says: "`createAuthInstance` hands better-auth exactly this string and `betterAuthEndpointPath` reads the same call" The second half holds. The first no longer does, and must not: better-auth is handed the configured value, `getBasePath()` is its normalised form, and they differ exactly when a trailing slash is configured. The measurement above is why. (ea848f7 corrected the "single definition" half of that same sentence; this is the other half.) 2. ea848f7 says, of `getAuthIssuer()`: "(trailing slash KEPT, while better-auth is now configured without one)" and: "The changeset also declares the one re-selected class that was unnamed: a basePath configured WITH a trailing slash now configures better-auth without it, so ctx.baseURL loses the slash and better-auth's URL building (callbacks, magic-link, oauth-proxy) stops emitting a doubled `//`." Both described the tree at that commit correctly and are false of this one: better-auth is configured WITH the trailing slash again, `ctx.baseURL` keeps it, and no class is re-selected. The doubled `//` in better-auth's URL building is therefore still there for that configuration, exactly as on the merge base; it is not fixed here and is not claimed to be. Its point 1 also reads "`getBasePath()` collapses two of them". The count is unchanged — four readers of `this.config.basePath` existed, two remain (`getAuthIssuer()`, `getMcpResourceUrl()`) — but the two collapsed readers now meet in `configuredBasePath()`, not in `getBasePath()`. 3. ea848f7 ends: "Prose only — docblock, changeset and two test headers." Its stat is three files — the changeset, `auth-manager.ts` (the docblock) and ONE test file, `auth-manager-base-path.test.ts`, in which it touched the file header and one `it()` comment. "Two test headers" is wrong; the commit itself is otherwise accurate. 4. 0615484 says: "The docblock and the changeset both said the surviving `getAuthIssuer()` / `getMcpResourceUrl()` derivations were "reported" without saying where." At 3846349 only the docblock said "reported to the PM" (`auth-manager.ts:5499`); the changeset said "is not a tidy-up that belongs on this card" and contains the word "reported" zero times. The correction that commit made is right; the quote attributing it to both is not. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N * test(plugin-auth): pin the ownership walk to getBasePath(), the unguarded side Round 3 split the base path into `configuredBasePath()` (handed to better-auth, verbatim) and `getBasePath()` (normalised, what an adapter mounts on), and gave that split three discriminating pins. All three sit on ONE side of it: they turn red when the string handed to better-auth is normalised. The MIRROR mistake -- pointing `betterAuthEndpointPath` at `configuredBasePath()` instead of `getBasePath()`, the same confusion one method along -- had no pin at all, and the docblock at `configuredBasePath()` records that a draft of this card already picked the wrong accessor once. Measured on this tree by the round-3 delta review, under exactly that mutation: `auth-manager-base-path.test.ts` + `auth-catchall-fallthrough.test.ts` + `auth-catchall-yield.test.ts` answer 23 passed / 0 failed, while a 16-shape sweep flips 37 cells -- on 8 configured spellings (`/`, `/api/v1/auth/`, `api/v1/auth`, `api/v1/auth/`, `/api/v1/auth///`, `/auth/`, `/api/v9/identity/`, `auth`) `ownsRoute` for the owned `.../get-session` goes true -> false and `betterAuthEndpointPath` goes to `undefined`, while the handed string and `iss` stay correct. That is not cosmetic drift. `ownsRoute` answering `false` is what lets the auth catch-all YIELD better-auth's own 404s, so a downstream wildcard answers `200 {}` where a real refusal stood -- objectstack-ai#15928's class -- under a trailing-slash or no-leading-slash deployment only. The default composition configures the already-normalised spelling, which is why nothing here could see it. Three cases on the REAL instance, addressing `${getBasePath()}/get-session` -- the URL an adapter that mounts on `getBasePath()` actually produces, so they ask the shipped question rather than a copy of the expression. `/api/v1/auth/` and `api/v1/auth` discriminate; `/api/v1/auth` is the control that cannot, and is in the file to say why the default composition was blind. No behaviour changes: test file only. The confirming ablation -- prediction first, mutation asserted on disk by blob hash, restore proven by hash equality and an empty `git diff HEAD` -- is recorded on the pull request. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N --------- Co-authored-by: Claude <noreply@anthropic.com>
Prepares coordinated release of 7 packages in the monorepo using Changesets workflow.
Packages
@objectstack/spec@0.2.0- Core protocol definitions@objectstack/types@0.2.0- Shared type definitions@objectstack/objectql@0.2.0- Query language & runtime@objectstack/runtime@0.2.0- Execution environment@objectstack/client@0.2.0- Client library@objectstack/driver-memory@0.2.0- In-memory driver@objectstack/plugin-hono-server@0.2.0- REST API server pluginChanges
changeset version(0.1.x → 0.2.0 for all packages)v0.2.0Release Automation
Merging to
maintriggers.github/workflows/release.ymlwhich builds and publishes to npm usingNPM_TOKENsecret.Build artifacts verified: 187 JSON schemas generated, TypeScript compilation clean across all packages.
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.