Repository navigation
docs(changeset): scope two pending service-analytics BREAKING banners to the SQL echo on either strategy, and anchor the nested-relation note to its measured base - #21012
Conversation
…SQL echo on either strategy, and anchor the nested-relation note's base The field-level gate and the relationship-path admission both run in the call context generateSql() shares, ahead of the strategy choice, and the ObjectQL strategy's echo never reached the engine's guards: the echo moved from a printed statement to 403 on both strategies, not only on a SQL deployment. The nested-relation note's Why paragraph describes the base measured before those two gates landed; it now says so. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…two pending analytics notes to the doors they hold for The ObjectQL strategy already refused these queries on the query and the dataset doors, through the engine; on the SQL echo it printed the statement, because its generateSql renders without an engine call. Each sentence now names the two doors and says the echo printed. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
|
Generated by Claude Code |
Contract reviewServed-tier: Review of PR #21012 (Part of #20917, #20933 and #20887) at its head ① Derived judgmentsThe diff. Three files, +9/−5, two commits ( The five rewritten sentences, each judged on (i) true, (ii) no new claim, (iii) backed by the cited reading.
What is deliberately not changed, and whether it should have been. The two ADR-0087 markers of the #20917 and #20933 notes are byte-identical (the #20917 marker's rationale still says the engine "already refuses" such queries "on the ObjectQL strategy", without the echo's scope). The marker is the gate's disposition input; The ADR-0087 reading and the Disclosure. The rewritten sentences name doors (route paths already present in all three notes) and strategies; no field spelling, no value, no request shape. The PR body and both dev reports state the probe by class, door and role only. Holds. ② Semver level
③ Boundary flagsRound 0's report (
Implemented-by: VERDICT: PASS |
…the error-code ledger lists its two refuse() codes (objectstack-ai#21106) (objectstack-ai#21150) Fixes objectstack-ai#21106 Clause-②: yes Three release-text follow-ups from the at-tier contract review of PR objectstack-ai#21084 (comment `5926057594`), all due before Version Packages objectstack-ai#20639 next picks up PR objectstack-ai#21084. Each sentence was checked against the code at PR objectstack-ai#21084's merge commit `8368f1c0`. The target files are byte-identical between `8368f1c0` and this branch's base (`git diff --stat` printed nothing). No package source changes. 1. **A deliberate correction of the stage-① pending note.** `.changeset/20281-connector-sync-moved-to-mapping.md` said `connectorSource` was "declared, not yet executed" and that "Nothing executes it in this release". The stage-② note `.changeset/20919-spec-connector-source-live.md` says the executor reads it. Both compile into the same `@objectstack/spec` CHANGELOG version. 2. **The "provenance, not identity" ledger sentence** for this release's `ERROR_CODE_LEDGER` face changes, in a new spec note, `.changeset/21106-error-code-ledger-provenance-rows.md`. 3. **The two `@objectstack/service-automation` provenance rows**, `MAPPING_NOT_FOUND` and `UNSUPPORTED_TRANSFORM`, in `packages/spec/src/api/error-code-ledger.zod.ts`. A hand pin in the provenance gate's own test, `packages/spec/scripts/check-error-code-provenance.test.ts`, guards them. ## 1 · The stage-① note correction | | Before | After | |:---|:---|:---| | Lead label | "**Added (declared, not yet executed):**" | "**Added:**" | | Last sentence of that paragraph | "Nothing executes it in this release, and `os validate` / `os build` warn when it is authored." | "The connector sync executor, `@objectstack/service-automation`'s `pullConnectorSource` (objectstack-ai#20919), reads it; nothing schedules a pull until the `job` stage lands." | Everything else in the file is byte-identical: the front matter, the summary line, the BREAKING banner, the FROM → TO table and the retirement kit. The diff is +4 / −3 lines in one paragraph. **Readings at `8368f1c0`:** - **"reads it".** `packages/services/service-automation/src/connector-pull.ts` `pullConnectorSource` (:209) reads the mapping through `getMetaItem` and its `connectorSource`. It makes one action call and writes through `runImport`. The liveness rows say the same: `packages/spec/liveness/mapping.json`, `connectorSource` `live`, evidence `connector-pull.ts#pullConnectorSource`. - **"nothing schedules a pull until the `job` stage lands".** Outside tests, `pullConnectorSource` has three places in its own package: the definition, the index re-export and the plugin method (`plugin.ts:621`). No package source calls the plugin method. - **Why the review's suggested clause "`os validate` / `os build` warn when it is authored" is NOT kept.** It is false at `8368f1c0`, measured: - The ledger row is `live` with `authorWarn: true`. `lintLivenessProperties`'s `describe()` (`packages/lint/src/lint-liveness-properties.ts`) has no `live` branch. It throws its sentinel for that pair, by design: its own header calls the pair "a ledger authoring mistake". - Through lint's source, against the built spec: `runAuthoringRules('validate', …)`, the call `os validate` makes (`packages/cli/src/commands/validate.ts:522`), THREW `lintLivenessProperties: ledger entry has unrecognised status "live"` for a stack whose `mappings[]` carries `connectorSource`. The control without `connectorSource` gave 0 findings. - The runtime metadata-write door (`runRuntimeAuthoringRules`) answered one `authoring-rule-threw` advisory in place of the liveness warning. - Read at `99398542b` and again at this head. `lint-liveness-properties.ts`, `authoring-rules.ts`, `runtime-gate.ts` and `liveness/mapping.json` have no diff between `8368f1c0` and `99398542b`. The CLI process itself was not run: its closure was not built here. - So the corrected note claims no warning. The defect is reported to the seat as a separate finding below and not changed here. ## 2 · The ledger sentence, and the counts behind it Measured on `ERROR_CODE_LEDGER` at `8368f1c0` against its parent `2742e537`, comments stripped: | Key | Parent | `8368f1c0` | Change | |:---|---:|---:|:---| | `@objectstack/rest` | 83 | 76 | −7: `AMBIGUOUS_MATCH`, `BLANK_MATCH_KEY`, `CONCURRENT_UPDATE`, `ERR_DATASOURCE_UNAVAILABLE`, `NO_MATCH`, `SUMMARY_RECOMPUTE_FAILED`, `UNIQUE_VIOLATION` | | `@objectstack/core` | 12 | 17 | +5: `AMBIGUOUS_MATCH`, `BLANK_MATCH_KEY`, `NO_MATCH`, `SUMMARY_RECOMPUTE_FAILED`, `UNSUPPORTED_TRANSFORM` | | `@objectstack/types` | absent | 3 | new key: `CONCURRENT_UPDATE`, `ERR_DATASOURCE_UNAVAILABLE`, `UNIQUE_VIOLATION` | | owner keys | 30 | 31 | | | union (`ErrorCode`) | 280 | 280 | none added, none removed | **The card and the review say rest lost −8. The measurement is −7.** Eight codes moved (5 to core and 3 to types), but rest kept `UNSUPPORTED_TRANSFORM`, which `resolveNamedMapping` still stamps. Literal counts in `packages/rest/src` non-test source at `8368f1c0`: 0 for each of the seven, and 1 for `UNSUPPORTED_TRANSFORM`. The note says seven. The sentence follows the objectstack-ai#20206 (`5f9d7d78`) and objectstack-ai#19441 (`3f9e2eaa`) paragraphs. It names both steps of this release's face change, the objectstack-ai#20919 move and this PR's two rows, and says the union, the wire and the HTTP answers are unchanged. ## 3 · The two rows, and why a pin rather than a wider gate - **Stamp sites at `8368f1c0`** (unchanged since). `connector-pull.ts:234` `refuse('MAPPING_NOT_FOUND', 404, 'mapping_not_found', …)` and `:303-304` `refuse('UNSUPPORTED_TRANSFORM', 400, 'unsupported_transform', …)`. Both go onto `ConnectorPullError.code`, and the class is exported from the package index. - **Both codes are registered extension codes.** They are listed under `@objectstack/rest`, and `UNSUPPORTED_TRANSFORM` under `@objectstack/core` too. Neither is in `errors.zod.ts`. The executor's other five codes are standard-catalog members and owe no row: `VALIDATION_ERROR`, `EXTERNAL_SERVICE_ERROR`, `INTEGRATION_ERROR`, `SERVICE_UNAVAILABLE`, `INVALID_FIELD`. - **Placement.** The rows go in the key's existing ASCII order: `MAPPING_NOT_FOUND` after `INVALID_SIGNAL`, and `UNSUPPORTED_TRANSFORM` last. One comment records the stamp sites, the statuses and the reachability reading: no HTTP door on this tree, so the thrown value is the boundary. No other package's rows were touched. - **Not widened.** `check:error-code-provenance`'s header declares it blind to a helper indirection (a `makeError(code, …)` call site). It also says "Widening is a gate-population change with an unmeasured blast radius — its own card, never a rider". So this PR keeps the gate's patterns and pins the two rows by hand instead. - **Measured for the seat, read-only.** The `refuse('CODE', …)` call-site form stamps a registered code at 7 sites in 3 packages: core `artifact-packages.ts` ×4, runtime `artifact-collections.ts` ×1, and these two. Before this PR, these two were the only unlisted ones; after it, none is unlisted. - **The pin.** A new block in the gate's test file has two halves. One pins the blind spot itself: `scanSourceText` finds no site in a `refuse('X', …)` call. The other asserts that `ERROR_CODE_LEDGER['@objectstack/service-automation']` lists each code. It reads the ledger module inside `packages/spec`, so the suite still reads nothing outside its package. - **Ablation, two legs** through `scripts/ablation-replace.mjs` in WRAP mode, run from the committed state at `c227eb366`: - Removing the `MAPPING_NOT_FOUND` row gave `1 failed | 16 passed (17)`: "expected [ …(9) ] to include 'MAPPING_NOT_FOUND'". - Removing the `UNSUPPORTED_TRANSFORM` row gave `1 failed | 16 passed (17)`, on that code's case. - Each leg restored the file (blob `229345964e13` = HEAD, `git diff HEAD` empty). - A first attempt at leg one was refused by the tool before any test ran: its replacement text already existed in the file. The anchor was changed and the leg re-run. That first attempt is not counted as a run. ## Changeset gate: no `skip-changeset`, and `Check Changeset` stays red by design This PR edits a pending changeset that it did not add. `node scripts/check-empty-changeset.mjs --base origin/main` exits 1 and refuses `.changeset/20281-connector-sync-moved-to-mapping.md` as the DELIBERATE CORRECTION class. The precedents are PR objectstack-ai#20991 and PR objectstack-ai#21012. Ruling D on objectstack-ai#18375 says `skip-changeset` is never applied to a PR that edits an existing changeset, so no label is applied. `Check Changeset` is not a required context. **Confirmation requested in writing on this PR:** the stage-① note's `connectorSource` paragraph now says the executor reads the binding and nothing schedules a pull yet. It no longer says nothing executes it or that `os validate` / `os build` warn. ## `Clause-②: yes`, not the claim's `no` The claim (`5926939917`) and the dispatch say `patch` with `Clause-②: no`. The dispatch also says not to keep `no` silently if the diff widens a public surface, and it does: - Two literal members are added to `ERROR_CODE_LEDGER['@objectstack/service-automation']`, a published `as const` face. - The ledger header (the objectstack-ai#16404 ruling) names this ledger, together with `StandardErrorCode`, as the published contract face for error codes. - The two precedents this card names, objectstack-ai#20206 and objectstack-ai#19441, each declared a provenance-row addition as `@objectstack/spec` `minor` with `Clause-②: yes`. Both say "What widens is the per-package face". So the new note is `minor` with `Clause-②: yes`, and this body's second line matches it. No accept set changes: the `ErrorCode` union is unchanged. Every package is in the one `fixed` group, and `@objectstack/spec` already has a pending `minor`, so the released version is the same either way. The seat can flip it back if it reads the precedent differently. ## Verification at `5748c8ddd` (base `99398542b`, merged with `origin/main` `39ab2940e`) - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (exit 0) derived 87 commands from the four changed paths. Each ran on this head, with its exit code captured before any pipe: - **84 exit 0.** Among them: `check:error-code-provenance` ("scanned 2636 files; 335 registered-code stamp site(s): 316 listed, 19 waived" / "OK"), `check-adr-0087-registration.mjs --base origin/main` ("this PR adds no declared-breaking changeset (2 non-breaking changeset(s) seen)"), `check-changeset-no-major.mjs --base origin/main` ("This diff introduces no `major` bump"), `check:api-surface`, `check:authorable-surface`, `check:docs`, `check:liveness`, `check:error-code-casing`, `check:dispatcher-error-vocabulary`, `check:cross-package-test-inputs`, `check:nul-bytes`, `check:query-options-erasure` and `check:type-check-debt`. - **1 exit 1, by design:** `check-empty-changeset.mjs --base origin/main`, the DELIBERATE CORRECTION class above. - **2 NOT MEASURED** (exit 3, `PREREQUISITE NOT MET`): `check:dual-build-cjs-loads` (needs a full `pnpm build`) and `check:lean-entry-closure` (needs `@objectstack/objectql` built). CI builds the tree. Reason: a built tree was not produced here. - Reconciled: `dispatch-gates.mjs --ran` exit 0, "87 derived famil(ies) accounted for — 85 run, 2 NOT-MEASURED". - `pnpm --filter @objectstack/spec build`: `VERDICT command-exit 0`. Then `pnpm --filter @objectstack/spec check:generated`: "All 15 generated artifacts are up to date". - `vitest run --project local` on `src/api` plus `scripts/check-error-code-provenance.test.ts`: 47 files / 1546 tests passed. The 12 other spec test files that read the ledger: 182 tests passed. - `pnpm --filter @objectstack/spec typecheck`: exit 0. That covers `tsc`, `check:scripts-typecheck`, and `check:test-typecheck` ("52 file(s) / 246 error(s) / 135 pinned signature(s) held", unchanged). - Main moved during the run: `origin/main` gained 13 commits after the branch point. None touches the four files. The delta against `39ab2940e` is exactly them (+70 / −4). ## Acceptance notes - **Read and kept in the stage-① note:** - "Runtime behaviour is deliberately **unchanged**: no connector sync ever ran." This is about the retired connector-attached keys (`syncConfig` / `fieldMappings`), and it stays true of them. - "It carries no cadence (a `job` sets that)." This describes the design; the corrected clause says nothing schedules a pull until the `job` stage lands. - **Reported to the seat, not changed here:** - **The liveness lint throws on `connectorSource`.** The `live` + `authorWarn` row reaches `describe()`'s sentinel throw, so `os validate` / `os build` stop with an internal lint error, and the runtime door answers `authoring-rule-threw`, instead of warning on an authored `connectorSource`. The comments in `authoring-rules.ts` and `runtime-gate.inert-type-writes.test.ts` still say it warns. The fix is a choice for its own card: teach `describe()` a caveat branch for `live`, or change the row. - **`turbo` 2.11.5 edits `AGENTS.md`.** It arrived with the development-dependencies bump (`840ec9dab`, now on `main`). On every turbo invocation it sees as an AI agent's, it appends a managed `turborepo-agent-rules` block to `AGENTS.md`, a Tier H governed surface, and `turbo.json` declares no `agentGuidance: false`. Measured here: `pnpm exec turbo run build --filter='@objectstack/lint...'` and `pnpm check:type-check-debt` each left `M AGENTS.md`, +11 lines. Each time it was restored with `git checkout HEAD -- AGENTS.md` (blob `e9e211fc` = HEAD) and never committed. An agent that commits with `-a` would carry it into its PR. --- _Generated by [Claude Code](https://claude.ai/code/session_017VaLJnYwhPsanVCe9dMCJU)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20917
Clause-②: no
Part of #20933 · Part of #20887
A follow-up on three landed cards. It corrects one sentence in each of three release notes of
@objectstack/service-analyticsthat are still pending onmain, before release PR #20639 consumes them. The order is this lane's dispatch note on #20917. The reasons are thedomain:specseat's pointer5922364600on #6021 (two banners) and the named follow-up in section ② of the delta review5922352898on PR #20916 (one paragraph). PR #20991 is the precedent: it makes the same banner correction to #20935's note, and that note is not touched here. No code, test or other file changes.The three sentences
.changeset/20917-analytics-field-permission-gate.md, the BREAKING bannerPOST /api/v1/analytics/sqlwhichever strategy serves the cube.".changeset/20933-analytics-relationship-path-admission.md, the BREAKING bannerPOST /api/v1/analytics/sqlwhichever strategy serves the cube.".changeset/20887-analytics-nested-relation-engine-answer.md, the first sentence of WhyThe parenthesis in the third row is unchanged and elided here. Both banners keep their bold. Everything else in the three files is byte-identical: front matter, summary line,
Clause-②line, ADR-0087 disposition marker and every other paragraph. Each file's diff is one line (+1/−1).The readings behind each sentence
Banner of the #20917 note
Before, by source, at
95555e71(the parent of #20931's landing1571aedc):packages/services/service-analytics/src/analytics-service.ts:2305-2333:generateSql()runscallCtx(2329), resolves a strategy (2330) and returns that strategy'sgenerateSql(2333).analytics-service.ts:1283-1327:callCtxruns the object-level admission (1308) and the read-scope pre-pass. It has no field-level gate. No non-test source in the package namesgetReadableFieldsat that commit (git grepexit 1). The control is the landing1571aedc, where it is named in three files.strategies/objectql-strategy.ts:372-638: the ObjectQL strategy'sgenerateSqlrenders the statement from the cube definition and returns it (638). It makes no engine call, so it never reaches the engine's field guards. That strategy'sexecute()reachesengine.aggregate, which is why the query door already refused on it.After, on
mainata5bce40888:analytics-service.ts:1453-1515:callCtxruns the field-level gate (1487) after the object admission (1481).generateSql()callscallCtxbefore it resolves a strategy (2580).Banner of the #20933 note
Before, by source, at
83480c6a(the parent of #20962's landing5f6b63a6):analytics-service.ts:1452-1507:callCtxadmits objects overqueryObjects(1477). That set iscubeObjects(1584-1588, 1596-1608): the base object and the declared joins only. An object reached through an undeclared relationship path is not in it.getReadableFields. That answer is field-level only and never asks about object-level read (packages/plugins/plugin-security/src/security-plugin.ts:5410-5412,computeReadableFields5440-5461 overresolveProjectionFieldMask5517 ff.). Object-level read is the separatecanReadObject(5641). So the field gate does not refuse a readable field on an unreadable related object.objectql-strategy.tsis byte-identical at95555e71,83480c6aand5f6b63a6(git diff --quiet, exit 0 for both ranges).generateSqlpasses a one-hop cross-object dimension throughplanCrossObject(895 ff.). That function throws only for the out-of-envelope shapes: a cross-object time dimension, measure or filter, a multi-hop dimension, a non-recombinable measure. The one-hop dimension renders a LEFT JOIN (461-466), and the statement is returned (638).After, on
mainata5bce40888:queryObjects(1607-1616) adds every object a named member reads.generateSql()resolves a strategy (2580).Measured after, on both strategies
The measurement was a scratch probe at
a5bce40888, copied intopackages/rest/srcfor one run and removed by an EXIT trap. It was never committed;git status --porcelainwas empty afterwards.os-verify-lock.shfirst:turbo run build --concurrency=1over@objectstack/service-analytics...,@objectstack/plugin-security...,@objectstack/objectql...and@objectstack/driver-sql.... That is 19 tasks,VERDICT command-exit 0.pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2on the one file gave 1 file / 2 tests passed,VERDICT command-exit 0.SecurityPluginoverObjectQLonSqlDriver(SQLite), andAnalyticsServicePluginover the same engine. There are two compositions:native(the plugin's own capabilities) andobjectql(narrowed to the engine-aggregate path).AnalyticsQueryRequestSchema). It then went togenerateSql, the callPOST /api/v1/analytics/sqlmakes (packages/runtime/src/domains/analytics.ts:141-159). The thrownstatusis the HTTP status (packages/runtime/src/dispatcher-plugin.ts:646-649).nativeobjectql403 PERMISSION_DENIED403 PERMISSION_DENIED403 PERMISSION_DENIED403 PERMISSION_DENIED403 PERMISSION_DENIED403 PERMISSION_DENIEDNativeSQLStrategy)ObjectQLStrategy)NativeSQLStrategy)ObjectQLStrategy)Before and after both hold for both banners. So each banner is scoped the way PR #20991 scoped #20935's. The note's own pins in
packages/rest/src/analytics-field-permission-gate.test.tsandanalytics-relationship-path-admission.test.tsassert the same echo refusals on both compositions. The probe adds the system-caller and readable controls, and it ran the bodies through the door's schema.The #20887 Why paragraph
The dev measured on the branch point of PR #20916,
00a92e18da. That is the parent of its first commit,5687276296.The base predates both gates.
git merge-base --is-ancestor 00a92e18da Xexits 0 for X =95555e71,1571aedc,83480c6aand5f6b63a6, so both landings descend from it. The reverse,1571aedcagainst00a92e18da, exits 1. Its control leg,00a92e18da~200against00a92e18da, exits 0, and the repository is not shallow.Clause one, "answered rows for a condition on a field the caller cannot read", held on that base:
getReadableFields(git grepexit 1; control: six hits inanalytics-service.tsonmain).callCtx(analytics-service.ts:1283ff. at00a92e18da) runs only the object admission (1308) and the read-scope pre-pass.strategies/filter-normalizer.ts:1296-1299). The native strategy joined the declared include.5916988260on #20802 analytics half (domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887) records the measured rows.Clause two, "without the declared join it named a table that does not exist (500)", held on that base:
strategies/native-sql-strategy.ts:771-783falls back to the relationship name as the joined table when no join is declared.analytics-service.ts:1266-1268;native-sql-strategy.ts:575-576).analytics-service.ts:1404,1416ff.). So nothing refused before the statement ran.The paragraph is anchored to that base in the delta review's own words; the clauses are kept.
The ADR-0087 gate's reading
breakingDeclarationandreadDispositionwere re-run on each note ata5bce40888and at this head. All three notes read the same on both:breaking: true;BREAKING,bangandclause-②-narrowing;not-required (no-migration-prescription).The gate itself counts the three as inherited, not introduced: it skips a changeset already breaking at the branch point.
Changeset gate: no
skip-changeset, andCheck Changesetstays redThis PR edits three pending changesets and adds none, so
Check Changesetgoes red by design.check-empty-changeset.mjs --base origin/mainexits 1 and refuses all three files as the DELIBERATE CORRECTION class. Ruling D on #18375 saysskip-changesetis never applied to a PR that edits an existing changeset, so no label is applied.Check Changesetis not a required context.Confirmation: this lane's at-tier contract review record on this PR's head judges each rewritten sentence above. The sentences are:
POST /api/v1/analytics/sqlon either strategy;domain:services): the cube read and the analytics read scope answer{ relation: { field: value } }as the engine seam now serves it — as the caller, capped, one answer on every face #20887 note's Why, now anchored to the base before the field-level gate and the relationship-path admission landed.Clause-②: nowas checked againstscripts/pm/clause2-line.mjs. A wording edit to pending notes widens no accept set and adds no public surface, so the value isno. With no arm, the line declares no direction.Verification at
56fc0e77e3node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(exit 0) derived 19 commands from the three changed paths. Each ran on this head, with its exit code captured before any pipe.check-adr-0087-registration.mjs --base origin/mainand--self-testcheck-changeset-no-major.mjs --base origin/mainand--self-testcheck-closing-keyword-parity.mjsand--self-testcheck-comment-mask-corpus.mjscheck-empty-changeset.mjs --self-testpm/release-rehearsal-clone.mjs --self-testcheck:changeset-gate-self-tests,check:driver-memory-census,check:gitlink-declared,check:nul-bytes,check:objectui-changeset,check:pm-changeset-deadline-census,check:published-files,check:refd-timer-probe,check:watch-hint-literalcheck-empty-changeset.mjs --base origin/main, the DELIBERATE CORRECTION class above.check-changeset-fixed.mjs(its roster lives under.changeset/) exited 0.dispatch-gates.mjs --ranwith the recorded exit codes exited 0. It read "19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN".origin/maingainedf8178ffeceafter the branch point. It touches none of the three notes, and all three are still present there.Acceptance notes
Patch rounds (the seat's append from the dev's report on #20917; the dev writes a body only once)
Patch round 1
At
db64c37690, on the seat's note5923016038on #20917, which takes the dev's class (a) finding into this PR. Two sentences beside the corrected banners are qualified to the doors they hold for. Nothing else changes: each is still one sentence, rewrapped in place, and both ADR-0087 disposition markers are byte-identical..changeset/20917-analytics-field-permission-gate.md, What changed, last sentencePOST /api/v1/analytics/queryandPOST /api/v1/analytics/dataset/query, as the data API did, but printed the statement onPOST /api/v1/analytics/sql.".changeset/20933-analytics-relationship-path-admission.md, Refusals that change form, first sentencePOST /api/v1/analytics/queryandPOST /api/v1/analytics/dataset/query, thoughPOST /api/v1/analytics/sqlprinted the statement; on those two doors it now answers the analytics door's refusal rather than the engine's, the same one a declared join gets."Readings:
95555e71. On the query door, the ObjectQL strategy'sexecute()reaches the engine (objectql-strategy.ts:301) before it renders its own echo (350-354). The dataset door runs the sameexecute(), so both refused through the engine. The SQL echo'sgenerateSql(372-638) renders with no engine call, andcallCtxhad no field gate (analytics-service.ts:1283-1327). SoPOST /api/v1/analytics/sqlprinted the statement.83480c6a. On the query and dataset doors, a one-hop dimension through a related object goes throughexecuteCrossObject. ItsresolveFkAttr(1176 ff.) reads that object through the engine as the caller (1211-1215). The echo renders the join (461-466) and returns the statement (638), with no engine call, and the object was outside the admitted set (analytics-service.ts:1584-1608). After the change, onmain, the echo answers403 PERMISSION_DENIEDon both strategies (round 0's probe).ADR-0087 reading at
db64c37690: for both notes,breakingDeclarationreadsbreaking: truewith the signalsBREAKING,bangandclause-②-narrowing, andreadDispositionreadsnot-required (no-migration-prescription). Both are unchanged froma5bce40888, and the #20887 note reads the same.Gates at
db64c37690:dispatch-gates.mjs --commandsderived the same 19 commands. 18 exited 0.check-empty-changeset.mjs --base origin/mainexited 1 by design: the DELIBERATE CORRECTION class, with all three notes named.check-changeset-fixed.mjsexited 0.--ran: 19 derived, 19 run, 0 not measured.origin/main8055ff2279, which had changed one roster file, and gave the same 19 commands. None of the three notes changed onmainover that range.Acceptance note, wording kept: both ADR-0087 disposition markers are HTML comments, and they stay byte-identical. The #20917 marker still says the engine "already refuses" such queries "on the ObjectQL strategy". Like the two sentences above, that wording holds for the query and dataset doors, not for the SQL echo.
Generated by Claude Code