Repository navigation
fix(spec): connector preserves the ADR-0010 protection envelope instead of silently stripping it (#6362) - #6900
Merged
Conversation
`ConnectorSchema` tolerated the ADR-0010 protection envelope but never declared it, so every package-load round-trip through the schema dropped all seven `_`-prefixed keys with no error anywhere. Tolerate is not preserve. Both metadata load paths call `applyProtection` on every type, and since #6245 bound `DeclarativeConnectorEntrySchema` to `PUT /meta/connector/:name`, something re-parses that stamped body on every write. This is the quiet half of the pair #6245 fixed. `sharing_rule` is `.strict()`, so its undeclared envelope was REJECTED — a hard 422, loud, fixed there. `ConnectorSchema` is a plain `z.object`, so it ACCEPTED the envelope, answered success, and stripped it — leaving every reader of `extractProtection` / `resolveLockState` looking at an unlocked, unattributed, org-provenance item. Fix is the single `...MetadataProtectionFields` spread #6245's dev verified on `sharing_rule`. Pure-additive and internal; the schema stays non-strict, so an undeclared `_` key is still stripped. `webhook` was measured in the same pass and needs no change — it has carried the spread since #4001 batch 11 and all seven keys already survive. That reading is now pinned rather than left as a note. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sGk4SKHqGRgmmqUok1P8M
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…ope keys (#6362) `content/docs/references/integration/connector.mdx` is GENERATED from the spec schemas. The `...MetadataProtectionFields` spread adds seven optional keys to `ConnectorSchema`, so the reference page's property tables go with it. Regenerated with `pnpm --filter @objectstack/spec gen:schema && ... gen:docs`, never hand-edited. Caught by the `check:docs` gate in the TypeScript Type Check job. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011sGk4SKHqGRgmmqUok1P8M
os-zhuang
marked this pull request as ready for review
August 9, 2026 04:29
This was referenced Aug 9, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Aug 17, 2026
…AS (objectstack-ai#6931) (objectstack-ai#7116) `metadata-type-schemas.test.ts` holds the invariant that every metadata type either declares `...MetadataProtectionFields` or sits on an explicit debt list, and that debt list is empty with a comment calling it the end state — so the gate read as total coverage. It was not. The walk iterates `listMetadataTypeSchemaTypes()`, which deliberately excludes the three non-KIND stack collections bound in `UNREGISTERED_KIND_SCHEMAS` (webhook / connector / sharing_rule). Those three are real parse doors — objectstack-ai#6245 wired them to `PUT /api/v1/meta/:type/:name` — so the one automated check for "declares no envelope" never ran over any of them, and all three were judged one at a time by hand: sharing_rule surfaced as a hard 422, connector only after a silent seven-key strip and a separate card a day later (objectstack-ai#6362 / PR objectstack-ai#6900), and webhook was fine by accident of objectstack-ai#4001 batch 11 with nothing verifying it. A second `it.each` asserts the envelope property — and only that property — over `listUnregisteredKindSchemaTypes()`, sharing the same `objectShapes` walker and `rejectedEnvelopeKeys` probe as the registered walk so the two iterations cannot drift into judging the property differently. All three pass today, so this closes no live bug; the value is prospective, for a fourth entry. Reverse-verified in both directions, predicted before measured. Removing the spread from ConnectorSchema turns the new `connector DECLARES` case red while every case of the two original suites stays green — the blind spot, exhibited. Adding a fourth entry with no envelope turns its `DECLARES` case red while the objectstack-ai#4001 campaign count is unmoved. Scope fence, per the triage ruling on objectstack-ai#6931: this enrolls the three in the test's iteration only. `listUnregisteredKindSchemaTypes()` returns names and grants nothing — no MetadataTypeSchema membership, no DEFAULT_METADATA_TYPE_REGISTRY entry, no create seed, no authorization verdict, no place in the campaign count. `listMetadataTypeSchemaTypes()` output is byte-identical, and a pinned case keeps it that way, so objectstack-ai#2657's B/C decision stays open and unprejudged. Fixes objectstack-ai#6931 Claude-Session: https://claude.ai/code/session_01JJpiZS4AgkrDYwZ2Amh5mw Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 25, 2026
akarma-synetal
pushed a commit
to akarma-synetal/framework
that referenced
this pull request
Sep 28, 2026
…acts/ to the commits and ADRs that decided them (stage 1) (objectstack-ai#20326) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 1 of the staged sweep, covering `packages/spec/src/kernel/**` and `packages/spec/src/contracts/**` and nothing else. Later stages cover the other areas, so this PR carries `Part of`. Every comment or docblock site in these two areas that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123): **160 sites on 151 lines in 45 files, covering 47 numbers (152 lines rewritten)**. Each rewritten line now cites the object this repository controls that decided the matter: - an ADR or ruling record where one exists; - otherwise the commit in `origin/main` history that decided it. Each line also says in its own words what that object decided. Where nothing answers, the sentence keeps its reason in words and the number is gone: that happened for four numbers. Only comments changed. Every file keeps its line count (152 lines out, 152 in), so no line citation into these files moves. No code token moves (see the guard below). String literals carrying a dead number are tokens: 30 such sites are left as they were and listed below. **No citation number is added.** Every tracker number on an added line was already on the line it replaces. That includes the two PR numbers now standing beside their shas as convenience links: PR objectstack-ai#6900 beside `b5404f496`, and PR objectstack-ai#7211 beside `1507ba356`. ## Census: this stage's two areas, before and after **Instrument.** This is PR objectstack-ai#20226's instrument: REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, over every distinct in-repo number cited in the two areas. The population is: - bare `#N`, `objectstack#N`, `framework#N`, and the `pre-#N` / `post-#N` spellings, with N of 100 or more; - excluding the ordinal heads the citation gate declares (Prime Directive, `PD`, decision `batch`) and `summon`. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 6 checkpoints per run. They read 18 of 18 lit (200) and 18 of 18 dead (404) in both runs. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites | kernel | contracts | lines | files | dead numbers | |---|---|---|---|---|---|---|---|---|---|---|---| | before | base `6a6a17b62`, probed 2026-09-27T20:14Z to 20:16Z | 445 | 398 | 47 | 0 | **190** | 106 | 84 | 180 | 45 | 47 | | after | head (probe at `eda5c6b27`, 2026-09-27T21:24Z to 21:26Z; source identical at the final head) | 415 | 398 | 17 | 0 | **30** | 18 | 12 | 29 | 14 | 17 | **Before, by class.** - 73 non-test docblock sites and 26 non-test line comments. - 22 test docblock sites and 39 test line comments. - 28 test string sites (describe and it titles, one assertion message). - 2 non-test strings. **After.** Only the 30 string sites remain. There are 0 comment sites. The head probe found no number newly dead since the base probe. PR objectstack-ai#20226's area table read kernel 105 and contracts 81 at an earlier base. This census reads 106 and 84 because it also counts the `pre-#N` / `post-#N` spelling: 3 dead sites. ## Per-number table The site counts give comments rewritten and strings left. The kind column says what each line now cites: - commit: the commit whose diff made the decision the line describes (read in each diff, not only in the subject); - ADR: the recorded decision; - dropped: the reason is kept in words and the number removed. | number | sites / files | rewritten / left (string) | anchor | kind | |---|---|---|---|---| | `objectstack-ai#5970` | 2/1 | 2/0 | `97e7e3caa`: `ActionSchema.visible` gains the boolean arm | commit | | `objectstack-ai#6083` | 1/1 | 1/0 | ADR-0122 phase 2, `53068c130`: find and findOne pinned to the parsed state | commit (ADR named) | | `objectstack-ai#6206` | 12/5 | 12/0 | `d7e0b4212`: maintainer ruling 2026-08-07, enforcement takes the full envelope with no per-site subset. One site cites `8e13ca876`, the route half that restored the five dropped fields. Two name the ruling in words where the same block already cites the sha | commit | | `objectstack-ai#6216` | 2/2 | 2/0 | `f586f1a89`: one ExecutionContext assembler, closed field-set pin | commit | | `objectstack-ai#6300` | 2/1 | 2/0 | `74155c735`: find and findOne accept the author state | commit | | `objectstack-ai#6361` | 1/1 | 1/0 | `90bbf2510`: notification-list `cursor` retired on both halves | commit | | `objectstack-ai#6362` | 3/2 | 3/0 | `b5404f496` (PR objectstack-ai#6900 beside it): `connector` keeps the ADR-0010 envelope | commit | | `objectstack-ai#6363` | 3/1 | 3/0 | `17d095413`: `unreadCount` counts the whole inbox, not the window | commit | | `objectstack-ai#6483` | 6/1 | 6/0 | ADR-0005 whitelist, executed by `ee58392e1`: nine unratified `allowOrgOverride: true` rolled back | commit (ADR named) | | `objectstack-ai#6511` (a PR) | 5/1 | 5/0 | `d7e0b4212`, its squash commit | commit | | `objectstack-ai#6523` | 7/3 | 6/1 | `aa4b90d9a`: sharing and approval enforcement take the full ExecutionContext | commit | | `objectstack-ai#6640` | 2/2 | 2/0 | `2ab1257c9`: `preserveAudit` is UPDATE-only, with a loud INSERT warn | commit | | `objectstack-ai#6723` (a PR) | 1/1 | 1/0 | `8ad609c69`, its squash commit: getObject's declared answer | commit | | `objectstack-ai#6725` | 6/2 | 4/2 | `1507ba356` (PR objectstack-ai#7211 beside it): facade object writes reach the map its reads use | commit | | `objectstack-ai#6745` | 2/1 | 2/0 | `7a5ef0008`: the getObject-equals-get conformance pin | commit | | `objectstack-ai#8715` | 3/3 | 3/0 | `2c86fe3ea`: the retirement pin form over `export-origins/` | commit | | `objectstack-ai#8794`, `objectstack-ai#8836` | 1/1 each | 1/0 each | `1850ebbb0`: measured the filter-reuse invariant and pinned it | commit | | `objectstack-ai#10194` | 7/2 | 6/1 | `2306a765c`: theme and analytics_cube bound at the /meta door | commit | | `objectstack-ai#10238` | 1/1 | 1/0 | none: whether cube authoring is live end to end is still its own measurement (`559041d39` leaves it open) | dropped | | `objectstack-ai#10338` | 2/1 | 1/1 | `d2619fd0c`: `ApiEndpoint.target` optional, the publish gate holds the requirement | commit | | `objectstack-ai#10485` | 5/2 | 5/0 | `35ad101bc`: `themes` carrier and ThemeSchema retired | commit | | `objectstack-ai#10627` | 3/2 | 3/0 | `be21955ba`, whose message records that controlled census | commit | | `objectstack-ai#10724` | 16/5 | 15/1 | `be21955ba`: nine dead `contributes` members tombstoned | commit | | `objectstack-ai#10726` | 6/3 | 4/2 | `bc56e1881`: `contributes.routes` retired, ruled Option B | commit | | `objectstack-ai#10812` | 2/2 | 2/0 | none: the cloud leg's clean close is kept as its date (2026-08-24, which `be21955ba` records) | dropped | | `objectstack-ai#11071` | 1/1 | 1/0 | `50fb191dc`: `os generate` file names derived from the registry, with the parity pin | commit | | `objectstack-ai#11330` | 2/1 | 1/1 | `a9ee98992`: trust-tier text states publish-gate-only enforcement | commit | | `objectstack-ai#11331` | 2/1 | 2/0 | none: an open "tracked on" pointer, and the enforce leg is unbuilt. The lines now say so | dropped | | `objectstack-ai#11332` | 9/2 | 8/1 | `dce5cd4f0`: three dead manifest containers retired | commit | | `objectstack-ai#11333` | 1/1 | 1/0 | `aaacf1d5c`: the commit that corrected the permissions half | commit | | `objectstack-ai#11350` | 1/1 | 1/0 | `ece4dad31`, which records the 2026-08-23 entry-nameability ruling | commit | | `objectstack-ai#11504` | 1/1 | 1/0 | `f90e82024`: `FLOW_INPUT_SCHEMA_INVALID` registered | commit | | `objectstack-ai#11741` | 5/2 | 4/1 | `b706af987`: `SendEmailInput.organizationId` | commit | | `objectstack-ai#11846` | 11/3 | 7/4 | `0c2334f6c`: preview mode retired | commit | | `objectstack-ai#12010` | 5/2 | 3/2 | none: that `ConnectionEngineLike` inventory is described in words | dropped | | `objectstack-ai#12165` | 1/1 | 1/0 | `b307bfd2a`: the glob-discovery disposition recorded beside `filePatterns` | commit | | `objectstack-ai#12248` | 19/4 | 15/4 | `8425c17cc`: the five ruled engine members adopted, getObject typed | commit | | `objectstack-ai#13135` | 9/7 | 8/1 | `9e0ba21a1`: paper customization protocol retired. ADR-0126 section 6 wall 4 stays cited where the line had it | commit (ADR named) | | `objectstack-ai#13608` | 2/1 | 2/0 | `fc9ba76a5`: eligibility held at redemption | commit | | `objectstack-ai#14143` | 2/2 | 2/0 | `f19475c0a`: the handler-face `ctx.recordLoadDenied` signal | commit | | `objectstack-ai#14192` | 4/1 | 0/4 | none rewritten: test titles only | (strings) | | `objectstack-ai#14722` | 2/1 | 2/0 | `23c72be3c`: pinned the measurement that refuted that card's premise | commit | | `objectstack-ai#16559` | 2/1 | 1/1 | `c7aca0dce`: ResumeFailureReport declared once | commit | | `objectstack-ai#16786` | 3/2 | 2/1 | `6059b29c0`: `updateById` declares its answer | commit | | `objectstack-ai#17147` | 5/2 | 3/2 | `aaacf1d5c`: the granted permission set is registered and refuses nothing | commit | | `objectstack-ai#18335` | 1/1 | 1/0 | ADR-0090 D10's 2026-09-16 note: an API key is a credential | ADR | Every cited sha resolves to exactly one commit, and every one is an ancestor of `origin/main` (38 shas, each `merge-base --is-ancestor` exit 0). ## The 30 string sites left as tokens - **Test titles and one assertion message (28 sites).** - contracts: `data-engine.test.ts` (4), `objectql-engine.test.ts` (2), `email-service.test.ts`, `resume-failure-report.pin.test.ts`, `scoped-context.test.ts` and `sharing-service.test.ts` (1 each). - kernel: `manifest-unknown-keys.test.ts` (4), `manifest.test.ts` (4), `preview-mode-retirement.test.ts` (4, one of them an assertion message), `plugin-runtime-tier-truthful-text.test.ts` (3), and 1 each in `metadata-customization-retirement.test.ts`, `metadata-type-api-registration.test.ts` and `metadata-type-schemas.test.ts`. - **Non-test strings (2 sites).** Two `why:` strings on rows of the exported `METADATA_ROUNDTRIP_CASES` table in `contracts/metadata-service-roundtrip-conformance.ts` (lines 194 and 202). They ship in the package as data. No runtime path prints them to an author: both test drivers title each case by its `id`. So they are not author-shown text in the form D sense, and they are not rewritten here. ## Mechanical guard: no code token moves The check is a comments-stripped token comparison, base `6a6a17b62` against the head. It uses the TypeScript parser's leaf tokens, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 45 touched `.ts` files. - Real run: 60,827 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control: 0 changes, as expected (exit 0). - Positive control (a declaration inserted): 1 file flagged (exit 1). - Positive control (one digit changed inside a test-title string): 1 file flagged (exit 1). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included; it says only that provenance comments were re-anchored. Measured on the built package: rewritten docblocks reach `dist/**/*.d.ts`. For example, `8425c17cc`, `17d095413`, `aa4b90d9a` and `fc9ba76a5` each appear in 1 declaration file, and the positive control, a pre-existing `notification-service` docblock sentence, appears in `dist/contracts/index.d.ts`. Rewritten comments also reach the bundled `.js`: `be21955ba` appears in 20 files. The `src/**/*.zod.ts` sources ship verbatim through `files[]`. ## Gates (head `ebbea0b6e`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. It judged 23 citations: 21 resolve and 2 resolve as pull requests (the two convenience links). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 86 families, and all 86 exit 0. `--ran` reports 86 run, 0 NOT MEASURED, 0 unrun, and exits 0. Five of them (`check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:i18n`, `check:lean-entry-closure`, `check:type-check-debt`) first exited 3, PREREQUISITE NOT MET. They exited 0 after a full `turbo run build` of `./packages/*` (71 tasks, exit 0, under the shared verify lock). - **Build, tests, typecheck:** - `pnpm --filter @objectstack/spec build` exits 0. - `vitest run src/kernel src/contracts` in `packages/spec`: 99 files and 1,608 tests pass. That covers all 24 touched test files and every test here that reads contract source text. - `pnpm --filter @objectstack/spec typecheck` exits 0, including `check:test-typecheck`. ## Acceptance notes - **Base.** The branch is 5 commits behind `origin/main` (`4e0f72e8d`). None of those commits touches `kernel/`, `contracts/` or any file here, so there was no merge. - **No author-shown string in scope.** Nothing in these two areas is form D's; the migration-entry fields belong to objectstack-ai#20233. - **Instrument note, not filed.** The citation gate reads `pre-#N` and `post-#N` as a cross-repository qualifier (`pre-`), so it never judges them. In these two areas that is 18 sites at base, 3 of them dead. This census counted them by hand. - **`framework#N` is NOT MEASURED as a repository.** `objectstack-ai/framework` answers 403 to this session's REST gate. The five numbers cited that way resolve 200 in this repository. None of them is in the dead set. - **Seat 2's pointer.** The 21 `objectstack-ai#13003` notes in `packages/spec/liveness/permission.json` are outside this stage, as the claim records. --- _Generated by [Claude Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #6362
Premise — re-verified at
origin/main, and it heldConnectorSchemais still a non-strictz.objectwith no ADR-0010 protectionenvelope. Measured before any edit, by round-tripping a loader-stamped catalog
descriptor through the schema the metadata registry actually resolves for
connector:All seven keys gone,
success: true, no error anywhere. Tolerate is notpreserve.
Why this one outlived its two siblings
Both metadata load paths call
applyProtectionon every type, so apackage-loaded connector carries the envelope by the time anything re-parses
it — and since #6245 bound
DeclarativeConnectorEntrySchematoPUT /api/v1/meta/connector/:name, something re-parses it on every write.The pair splits by posture, and the quiet half is the one that survived:
sharing_rule.strict()connectorz.objectEvery downstream reader of
extractProtection/resolveLockStatethereforesaw an unlocked, unattributed,
org-provenance connector where the loader hadstamped a locked, package-owned one.
The fix
One
...MetadataProtectionFieldsspread onConnectorSchema— the shape#6245's dev verified on
sharing_rule. Pure-additive and internal: every keyis
_-prefixed and optional, no author-facing field changes, and nothing thatparsed before stops parsing.
ConnectorSchemastays non-strict (subtypes.extend()it), so the spread adds seven named keys, not a passthrough —an undeclared underscore key is still stripped, and that is pinned.
MANDATORY: the
webhookmeasurement — PRESERVED, no drop, no spread appliedThe card asked whether
webhookhad the same defect and to apply the samespread only if it did. It does not, so nothing was applied there.
Probe — the same round-trip used for
connectorabove, driven throughgetMetadataTypeSchema('webhook')(the/metawrite door #6245 bound) with afully populated seven-key envelope, asserting the parse output key by key:
all seven survive. Reading confirms it:
WebhookSchemahas carried...MetadataProtectionFieldssince #4001 batch 11, and the generatedintegration/WebhookConfig:_packageIdbaseline entry already recorded it.That reading is now a pin, not a note in a closed report
(
webhook.test.ts,[#6362] PRESERVES all seven envelope keys — measured, not assumed), so it cannot regress unobserved. The pre-existing #4001 test assertedonly two keys and only that the envelope was accepted — enough for "accepts",
not for "preserves", and an accepts-shaped assertion is exactly what was green
on
connectorthroughout.Reverse verification — direction predicted first, then measured
Prediction: removing the spread turns the new preservation pins red while
every pre-existing
connectorassertion stays green. That asymmetry isthe defect's signature — the unfixed schema parses the stamped body perfectly
well, so only an assertion over the parse output can see it.
Measured (spread removed via
git checkout origin/main -- connector.zod.ts,restored from the commit afterwards):
5 red, 186 green — including all pre-existing
connectortests and bothwebhookenvelope tests. Exactly the predicted direction.Tests
New
describe('ADR-0010 protection envelope (#6362)')inconnector.test.ts, driving the round-trip at all three levels, because thebase schema carrying the spread is only half an answer if the bound door drops
it:
ConnectorSchema— all seven keys asserted by value;DeclarativeConnectorEntrySchema— the/metawrite door meta: bind Zod schemas for webhook / connector / sharing_rule WITHOUT registering the kinds — close the unvalidatedPUT /metawrite hole (#2657 audit, option A) #6245 bound;getMetadataTypeSchema('connector')— the registry lookup, so a futurerebinding to a third shape cannot pass the first two and still strip in
production;
§3/§5 cross-field rules;
WebhookConfigSchema, so the inherited behaviour cannot regress through afuture
.extend()/.omit()on the connector side.No new test files, so the type-check coverage ledger is untouched.
Generated baselines
packages/spec/authorable-surface/integration.jsongains the sevenintegration/Connector:_*keys, regenerated by the spec build — matching what#6245 recorded for
security/SharingRuleandsecurity/CriteriaSharingRule.authorable-surface.base.jsonwas not rewritten.Generated by Claude Code