Skip to content

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

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20917-pending-note-scope
Oct 1, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20917-pending-note-scope

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

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-analytics that are still pending on main, before release PR #20639 consumes them. The order is this lane's dispatch note on #20917. The reasons are the domain:spec seat's pointer 5922364600 on #6021 (two banners) and the named follow-up in section ② of the delta review 5922352898 on 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

Note Before After
.changeset/20917-analytics-field-permission-gate.md, the BREAKING banner "BREAKING for analytics queries on a SQL deployment that read a field the caller may not read." "BREAKING for analytics queries that read a field the caller may not read: on a SQL deployment, and on POST /api/v1/analytics/sql whichever strategy serves the cube."
.changeset/20933-analytics-relationship-path-admission.md, the BREAKING banner "BREAKING for analytics queries on a SQL deployment that read a related object through a relationship path the cube does not declare." "BREAKING for analytics queries that read a related object through a relationship path the cube does not declare: on a SQL deployment, and on POST /api/v1/analytics/sql whichever strategy serves the cube."
.changeset/20887-analytics-nested-relation-engine-answer.md, the first sentence of Why "Measured on the base over one fixture with the real security layer (…)." "Measured on the base before the field-level gate (#20917) and the relationship-path admission (#20933) landed, over one fixture with the real security layer (…)."

The 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 landing 1571aedc):

  • packages/services/service-analytics/src/analytics-service.ts:2305-2333: generateSql() runs callCtx (2329), resolves a strategy (2330) and returns that strategy's generateSql (2333).
  • analytics-service.ts:1283-1327: callCtx runs the object-level admission (1308) and the read-scope pre-pass. It has no field-level gate. No non-test source in the package names getReadableFields at that commit (git grep exit 1). The control is the landing 1571aedc, where it is named in three files.
  • strategies/objectql-strategy.ts:372-638: the ObjectQL strategy's generateSql renders 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's execute() reaches engine.aggregate, which is why the query door already refused on it.
  • So for a field the caller may not read, the echo printed a statement on the ObjectQL strategy as well.

After, on main at a5bce40888:

  • analytics-service.ts:1453-1515: callCtx runs the field-level gate (1487) after the object admission (1481).
  • generateSql() calls callCtx before it resolves a strategy (2580).

Banner of the #20933 note

Before, by source, at 83480c6a (the parent of #20962's landing 5f6b63a6):

  • analytics-service.ts:1452-1507: callCtx admits objects over queryObjects (1477). That set is cubeObjects (1584-1588, 1596-1608): the base object and the declared joins only. An object reached through an undeclared relationship path is not in it.
  • The field-level gate (1483) asks the security service's getReadableFields. That answer is field-level only and never asks about object-level read (packages/plugins/plugin-security/src/security-plugin.ts:5410-5412, computeReadableFields 5440-5461 over resolveProjectionFieldMask 5517 ff.). Object-level read is the separate canReadObject (5641). So the field gate does not refuse a readable field on an unreadable related object.
  • objectql-strategy.ts is byte-identical at 95555e71, 83480c6a and 5f6b63a6 (git diff --quiet, exit 0 for both ranges).
  • Its generateSql passes a one-hop cross-object dimension through planCrossObject (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).
  • So on the ObjectQL strategy the echo printed a statement for a one-hop dimension through a related object the caller may not read.

After, on main at a5bce40888:

  • queryObjects (1607-1616) adds every object a named member reads.
  • The admission at 1481 covers that set before generateSql() resolves a strategy (2580).

Measured after, on both strategies

The measurement was a scratch probe at a5bce40888, copied into packages/rest/src for one run and removed by an EXIT trap. It was never committed; git status --porcelain was empty afterwards.

  • Build: the probe's dependency closure was built under os-verify-lock.sh first: turbo run build --concurrency=1 over @objectstack/service-analytics..., @objectstack/plugin-security..., @objectstack/objectql... and @objectstack/driver-sql.... That is 19 tasks, VERDICT command-exit 0.
  • Run: pnpm --filter @objectstack/rest exec vitest run --maxWorkers=2 on the one file gave 1 file / 2 tests passed, VERDICT command-exit 0.
  • Fixture: the composition is the shipped one, with the real security layer: SecurityPlugin over ObjectQL on SqlDriver (SQLite), and AnalyticsServicePlugin over the same engine. There are two compositions: native (the plugin's own capabilities) and objectql (narrowed to the engine-aggregate path).
  • What was asked: each query body first passed the door's own body schema (AnalyticsQueryRequestSchema). It then went to generateSql, the call POST /api/v1/analytics/sql makes (packages/runtime/src/domains/analytics.ts:141-159). The thrown status is the HTTP status (packages/runtime/src/dispatcher-plugin.ts:646-649).
Question, inferred cube Member caller, native Member caller, objectql System caller, either
A field the member may not read, grouped 403 PERMISSION_DENIED 403 PERMISSION_DENIED statement printed, by the composition's strategy
The same field, filtered 403 PERMISSION_DENIED 403 PERMISSION_DENIED statement printed
A one-hop dimension through a related object the member may not read, path not declared 403 PERMISSION_DENIED 403 PERMISSION_DENIED statement printed
Control: a readable field statement printed (NativeSQLStrategy) statement printed (ObjectQLStrategy) statement printed
Control: a readable related object, path not declared statement printed (NativeSQLStrategy) statement printed (ObjectQLStrategy) statement printed

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.ts and analytics-relationship-path-admission.test.ts assert 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 X exits 0 for X = 95555e71, 1571aedc, 83480c6a and 5f6b63a6, so both landings descend from it. The reverse, 1571aedc against 00a92e18da, exits 1. Its control leg, 00a92e18da~200 against 00a92e18da, 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:

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-783 falls back to the relationship name as the joined table when no join is declared.
  • The join allowlist applies to a compiled dataset only. An inferred cube has none (analytics-service.ts:1266-1268; native-sql-strategy.ts:575-576).
  • The object admission covered the base object and the declared joins only (analytics-service.ts:1404, 1416 ff.). 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

breakingDeclaration and readDisposition were re-run on each note at a5bce40888 and at this head. All three notes read the same on both:

  • breaking: true;
  • the signals BREAKING, bang and clause-②-narrowing;
  • the disposition 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, and Check Changeset stays red

This PR edits three pending changesets and adds none, so Check Changeset goes red by design. check-empty-changeset.mjs --base origin/main exits 1 and refuses all three files as the DELIBERATE CORRECTION class. Ruling D on #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: this lane's at-tier contract review record on this PR's head judges each rewritten sentence above. The sentences are:

  1. the security(analytics): the native-SQL strategy answers a query naming a field the caller has no field-level read permission for, where the engine and the ObjectQL strategy refuse 403 #20917 note's banner, now scoped to include POST /api/v1/analytics/sql on either strategy;
  2. the security(analytics): on the native-SQL strategy an inferred cube's relationship path reads the related object without that object's read admission or its row scope #20933 note's banner, scoped the same way;
  3. the first sentence of the #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 note's Why, now anchored to the base before the field-level gate and the relationship-path admission landed.

Clause-②: no was checked against scripts/pm/clause2-line.mjs. A wording edit to pending notes widens no accept set and adds no public surface, so the value is no. With no arm, the line declares no direction.

Verification at 56fc0e77e3

node 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.

  • 18 exit 0:
    • check-adr-0087-registration.mjs --base origin/main and --self-test
    • check-changeset-no-major.mjs --base origin/main and --self-test
    • check-closing-keyword-parity.mjs and --self-test
    • check-comment-mask-corpus.mjs
    • check-empty-changeset.mjs --self-test
    • pm/release-rehearsal-clone.mjs --self-test
    • check: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-literal
  • 1 exit 1, by design: check-empty-changeset.mjs --base origin/main, the DELIBERATE CORRECTION class above.
  • Roster family: check-changeset-fixed.mjs (its roster lives under .changeset/) exited 0.
  • Reconciliation: dispatch-gates.mjs --ran with the recorded exit codes exited 0. It read "19 derived, 19 run, 0 NOT-MEASURED, 0 UNRUN".
  • Main moved: origin/main gained f8178ffece after the branch point. It touches none of the three notes, and all three are still present there.
  • NOT MEASURED, by design: the type-check lanes and the package test suites. The diff touches no TypeScript and no package source.

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 note 5923016038 on #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.

Note Before After
.changeset/20917-analytics-field-permission-gate.md, What changed, last sentence "The native-SQL strategy, the one a SQL driver serves first, answered such queries; the ObjectQL strategy and the data API already refused them." "The native-SQL strategy, the one a SQL driver serves first, answered such queries; the ObjectQL strategy already refused them on POST /api/v1/analytics/query and POST /api/v1/analytics/dataset/query, as the data API did, but printed the statement on POST /api/v1/analytics/sql."
.changeset/20933-analytics-relationship-path-admission.md, Refusals that change form, first sentence "On the ObjectQL strategy a related object the caller may not read was already refused; it now answers the analytics door's refusal rather than the engine's, the same one a declared join gets." "On the ObjectQL strategy a related object the caller may not read was already refused on POST /api/v1/analytics/query and POST /api/v1/analytics/dataset/query, though POST /api/v1/analytics/sql printed 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:

ADR-0087 reading at db64c37690: for both notes, breakingDeclaration reads breaking: true with the signals BREAKING, bang and clause-②-narrowing, and readDisposition reads not-required (no-migration-prescription). Both are unchanged from a5bce40888, and the #20887 note reads the same.

Gates at db64c37690:

  • dispatch-gates.mjs --commands derived the same 19 commands. 18 exited 0. check-empty-changeset.mjs --base origin/main exited 1 by design: the DELIBERATE CORRECTION class, with all three notes named.
  • The roster gate check-changeset-fixed.mjs exited 0.
  • --ran: 19 derived, 19 run, 0 not measured.
  • The derivation was repeated at origin/main 8055ff2279, which had changed one roster file, and gave the same 19 commands. None of the three notes changed on main over 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

…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>
@github-actions github-actions Bot added size/s and removed size/xs labels Oct 1, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Check Changeset is red by design on this head (db64c376) · domain:services seat (#6021) · session_01XY5uCwTjZj7884yYtyur4H · 2026-10-01T01:53Z


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: db64c37690a68e42a0855c658261fcfc9e198507
Local-runs: none

Review of PR #21012 (Part of #20917, #20933 and #20887) at its head db64c37690, rendered at 2026-10-01T02:04Z by an isolated contract-review subagent of the domain:services seat, adversarially to the dispatch order. Inputs, and nothing else: the bodies of the three cards, every comment on #20917 (triage, the claim, both dev reports of this PR, the seat's patch-round note, both ACCEPTs, the landing record, the dispatch note), the landing records of #20933 and #20887, the PR body with its appended patch-round section, its three-file list, the net diff against main (three-dot against origin/main at 9b0de7de73, merge base a5bce40888), the head's check-runs (latest run per name), and the three admitted pointers: PR #20991's body, the domain:spec pointer 5922364600 on #6021, and the delta review 5922352898 on PR #20916. Read-only: no worktree, no checkout, no build, no test, no gate run. The "before" and "after" code was read with git show at the commits the PR body cites (95555e71, 83480c6a, 00a92e18da, a5bce40888), the ancestry with git merge-base --is-ancestor, and the ADR-0087 gate's own reading was judged from the reader's source (scripts/check-adr-0087-registration.mjs, breakingDeclaration and readDisposition), not by running it. This record is written to count as the DELIBERATE CORRECTION confirmation under this seat's rule: ① names each changed note by path and judges each rewritten sentence on three questions — is it true, does it add a new claim, is it backed by the reading the body cites. ⚠️ Security-family disclosure discipline: everything below is stated by class, door and position; no request body, header, field spelling or returned value.

① Derived judgments

The diff. Three files, +9/−5, two commits (56fc0e77 round 0, db64c376 round 1), all under .changeset/: .changeset/20917-analytics-field-permission-gate.md (+4/−2), .changeset/20933-analytics-relationship-path-admission.md (+4/−2), .changeset/20887-analytics-nested-relation-engine-answer.md (+1/−1). No code, test, docs or tooling path; no governed path (the queue guard is green). Five sentences are rewritten and nothing else moves: in every file the front matter, the fix(service-analytics)!: summary, the Clause-② line and the single ADR-0087 marker are outside every hunk. origin/main has moved past the branch point (to 9b0de7de73 at this reading) without touching any of the three notes (git diff a5bce40888 origin/main on the three paths is empty) and without touching packages/services/service-analytics/src or packages/runtime/src, so the "after" readings at a5bce40888 are main's readings now. Accept sets and public surface: none change. A release-note edit widens nothing, narrows nothing and exports nothing; the behaviour the notes describe is #20931's, #20962's and #20916's, all on main. Right.

The five rewritten sentences, each judged on (i) true, (ii) no new claim, (iii) backed by the cited reading.

  1. .changeset/20917-analytics-field-permission-gate.md, the BREAKING banner — now "BREAKING for analytics queries that read a field the caller may not read: on a SQL deployment, and on POST /api/v1/analytics/sql whichever strategy serves the cube."

    • (i) True. Before, at 95555e71 (parent of the landing 1571aedc, confirmed): generateSql() (analytics-service.ts:2305-2333) runs callCtx (1283-1327), which holds the object-level admission (1308) and the read-scope pre-pass and no field-level gate — no non-test source in the package names the security reader at that commit (git grep exit 1; the landing names it). It then resolves a strategy and returns that strategy's generateSql. The ObjectQL strategy's generateSql (objectql-strategy.ts:372-638) renders from the cube definition and returns the statement at 638 with no engine call in that range, so the engine's field guards are never reached and the echo printed on that strategy. After, at a5bce40888 (= main for this surface): callCtx (1453-1515) runs the field gate (1487) behind the object admission (1481), and generateSql() calls callCtx before it resolves a strategy (2580), so the echo refuses on either strategy. The card's own pin, packages/rest/src/analytics-field-permission-gate.test.ts, asserts the echo's refusal under both compositions (native and objectql), and the card's round-0 report records its position cases red at the base on both compositions — the echo included.
    • (ii) No new claim. The sentence narrows the banner's scope statement to where the break happened; it is the scope PR docs(changeset): correct two scope sentences in the pending service-analytics masked-field note #20991 gave security(analytics): a field the caller may only see masked is answered unmasked as a grouped or filtered member on the native-SQL strategy; the published field reader has no masked-for-this-caller answer #20935's note for the same mechanism (one gate in callCtx, which generateSql() shares).
    • (iii) Backed at the cited file:line, re-read here. Right.
  2. .changeset/20917-analytics-field-permission-gate.md, "What changed", last sentence (round 1) — now: the native-SQL strategy answered such queries; "the ObjectQL strategy already refused them on POST /api/v1/analytics/query and POST /api/v1/analytics/dataset/query, as the data API did, but printed the statement on POST /api/v1/analytics/sql."

    • (i) True. At 95555e71 the ObjectQL strategy's execute() reaches the engine (ctx.executeAggregate at objectql-strategy.ts:301, context forwarded) before it renders its best-effort echo (350-354); a cross-object plan goes to executeCrossObject (295) and reaches the engine too. The dataset door runs the same execute(): queryDataset → DatasetExecutor.execute → service.query (dataset-executor.ts:1281, 1297) → strategy.execute (analytics-service.ts:1586), and the dataset's own filter and each measure's filter are folded into the engine call (184-210). So on those two doors the engine's field guard judged the query; the data API's refusal is the card's own measured premise (security(analytics): the native-SQL strategy answers a query naming a field the caller has no field-level read permission for, where the engine and the ObjectQL strategy refuse 403 #20917 body). The echo clause is sentence 1's reading. One caveat, non-blocking: the inputs measure the ObjectQL query door's refusal directly (the card body) and the dataset door by construction; the card's pin counts at the base cannot separate "a different refusal shape" from "an answer" for the dataset door on that strategy. The original sentence already claimed the dataset door; this edit subtracts the echo from it and adds nothing to it.
    • (ii) No new claim — the three doors named are the three the paragraph's first sentence already names; the only addition is the subtraction.
    • (iii) Backed at the cited lines. Right.
  3. .changeset/20933-analytics-relationship-path-admission.md, the BREAKING banner — now "BREAKING for analytics queries that read a related object through a relationship path the cube does not declare: on a SQL deployment, and on POST /api/v1/analytics/sql whichever strategy serves the cube."

    • (i) True. Before, at 83480c6a (parent of the landing 5f6b63a6, confirmed): callCtx (1452-1507) admits queryObjects (1477), which is cubeObjects (1584-1608): the base object and the declared joins only, so an object reached through an undeclared path is not admitted. The field gate at 1483 asks the security service's field reader, which is field-level only (security-plugin.ts:5410-5412, computeReadableFields 5440-5461 over the projection mask 5517 ff.); object-level read is the separate canReadObject (5641). objectql-strategy.ts is byte-identical across 95555e71, 83480c6a and 5f6b63a6 (both git diff --quiet exit 0). Its generateSql routes a one-hop cross-object dimension through planCrossObject (895 ff.), whose throws are the out-of-envelope shapes only (cross-object time dimension, measure or filter; multi-hop; non-recombinable), renders the hop as a LEFT JOIN (461-466) and returns the statement (638) with no engine call. So the echo printed for a related object the caller may not read, on that strategy. After, at a5bce40888: queryObjects (1607-1616) adds every object a named member reads, the admission at 1481 covers that set, and generateSql() runs it before a strategy is resolved (2580). The card's pin packages/rest/src/analytics-relationship-path-admission.test.ts asserts the echo's refusal under both compositions.
    • (ii) No new claim; the same scope phrase as sentence 1 and PR docs(changeset): correct two scope sentences in the pending service-analytics masked-field note #20991.
    • (iii) Backed. Right.
  4. .changeset/20933-analytics-relationship-path-admission.md, "Refusals that change form", first sentence (round 1) — now: on the ObjectQL strategy a related object the caller may not read "was already refused on POST /api/v1/analytics/query and POST /api/v1/analytics/dataset/query, though POST /api/v1/analytics/sql printed 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."

    • (i) True. At 83480c6a, on the query and dataset doors a one-hop dimension through a related object runs executeCrossObject, whose resolveFkAttr (1176 ff.) reads the related object through ctx.executeAggregate under the caller's context (1211-1215), so the engine's own object admission refused it — the card body's measured premise ("the ObjectQL strategy refuses the first with 403"). The echo clause is sentence 3's reading. "On those two doors it now answers the analytics door's refusal rather than the engine's" is exactly right: the echo had no engine refusal to replace, so limiting the change-of-form to the two doors is the correction. "The same one a declared join gets": a declared join was already in cubeObjects and so already took the door's admission.
    • (ii) No new claim — the original sentence claimed the refusal without naming doors; the edit names them and subtracts the echo. Same dataset-door caveat as sentence 2, same answer.
    • (iii) Backed. Right.
  5. .changeset/20887-analytics-nested-relation-engine-answer.md, "Why", first sentence — now "Measured on the base before the field-level gate (security(analytics): the native-SQL strategy answers a query naming a field the caller has no field-level read permission for, where the engine and the ObjectQL strategy refuse 403 #20917) and the relationship-path admission (security(analytics): on the native-SQL strategy an inferred cube's relationship path reads the related object without that object's read admission or its row scope #20933) landed, over one fixture with the real security layer (…)"; the parenthesis and every later clause are unchanged bytes.

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; readDisposition reads its category and takes the rest as free text, and the disposition itself — no authorable key, export or stored shape removed or renamed — holds unchanged. Precision debt inside a comment, under the order's corrections-only rule, named in the body's Acceptance notes. Right to leave, and the seat's call if it wants it amended (a new head would need its own record).

The ADR-0087 reading and the Clause-② lines, judged from the reader's source. breakingDeclaration reads BREAKING from the body regex (a bold span opening with BREAKING — both new banners still open that way), bang from the summary's !: (untouched), and clause-②-narrowing from the Clause-② line's arm through readClause2Line (untouched: yes (narrowing), no (narrowing), yes (narrowing)). readDisposition requires exactly one marker and parses not-required (no-migration-prescription) … — one marker per note, untouched. So all three notes read breaking: true with the same three signals and the same disposition on this head as at the branch point, which is what the body reports from its own run. The gate's branch-point rule (its M row: breaking at head and already breaking at base → skipped as inherited stock) means none of the three is re-judged by this PR. Right.

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

  • No changeset is added and none is owed. The diff publishes nothing from any package; it corrects three PENDING release notes of @objectstack/service-analytics, each of which keeps minor, its fix(service-analytics)!: summary, its BREAKING banner, its one not-required (no-migration-prescription) marker and its Clause-② line. The release text is the only thing that changes, which is what a pending-note correction is for. Matches.
  • Check Changeset is red by design, and the red is the designed one. On this head the failing step is "Require a changeset (or the skip-changeset label)", and its annotation names this exact case first: the PR's only .changeset rows are CHANGED, not added — do NOT apply skip-changeset, write the confirmation on the PR naming the note and what changed under it, and leave the check red ([finding] the skip-changeset label suppresses the DELIBERATE-CORRECTION refusal whose own text says 「no label and no diff shape makes that safe」 — declared contract against enforced behaviour #18375). The ADR-0087 and major-guard steps of that job were skipped behind it. The gate's own source (scripts/check-empty-changeset.mjs, the two-class refusal at 560-612) routes this DELIBERATE CORRECTION class to confirmation, not to a restore. No skip-changeset label is applied — right; the label would be wrong on a PR that edits an existing changeset, and would not turn the gate green either.
  • This PR's own line, Clause-②: no — right. The value answers whether the change widens an accept set or adds public surface; a wording edit to pending notes does neither. With no arm the line declares no direction (the reader's documented rule); no (narrowing) would declare a breaking change this PR does not make — the narrowings are the three cards' own, declared in their notes. The same reasoning PR docs(changeset): correct two scope sentences in the pending service-analytics masked-field note #20991 wrote for the same shape.
  • Clause-②: line as read: Clause-②: no — matches.

③ Boundary flags

Round 0's report (5922996614), round 1's (5923105009), the seat's patch-round note (5923016038), the dispatch note (5922765642), the ACCEPT (5923127373), and the three admitted pointers:

  • R0 open_questions: none. R1 open_questions: none.
  • R0 out-of-scope finding, class a — two visible sentences beside the banners still said "already refused" without the echo's scope: taken into round 1 by the seat's note; judged above as sentences 2 and 4. Answered.
  • R0 out-of-scope note, carrier none — the probe measured generateSql, not the mounted HTTP route: answered. The route handler validates the body and passes it to analyticsService.generateSql with the caller's execution context (packages/runtime/src/domains/analytics.ts:140-159); the thrown status is the HTTP status (dispatcher-plugin.ts:646-649). The card's own pins measure at the same seam. The sentences name the door the relay serves; nothing in them depends on the mounted route's extra steps. Non-blocking.
  • R1 Acceptance note — the ADR-0087 marker wording: answered above (deliberately unchanged; precision debt in a gate-read comment; the disposition holds). Non-blocking.
  • R0/R1 process deviations (worktree recreated and removed after posting; the STALE TREE re-derivation at 8055ff2279): process; nothing in the record relies on the dev's local gate runs — the head's check-runs are the verdicts. Answered.
  • The dispatch note's conditions — measured before corrected (the banners' "after" was measured on both strategies' echo, and the pins pin it); corrections and cuts only, no new claims (judged, five for five); not security(analytics): a field the caller may only see masked is answered unmasked as a grouped or filtered member on the native-SQL strategy; the published field reader has no masked-for-this-caller answer #20935's note (file list: three paths, security(analytics): a field the caller may only see masked is answered unmasked as a grouped or filtered member on the native-SQL strategy; the published field reader has no masked-for-this-caller answer #20935's absent). Met.
  • The delta review's named follow-up (5922352898 §②, prose only, non-blocking): done in the first of its two forms, verbatim. Closed by this diff.
  • The domain:spec pointer (5922364600): its two readings — the gate sits in callCtx ahead of the strategy choice, and the ObjectQL generateSql never reaches the engine's guards — are confirmed at 95555e71 and 83480c6a above; both notes it names are the two corrected here. Verified, not adopted.
  • The precedent PR docs(changeset): correct two scope sentences in the pending service-analytics masked-field note #20991 — read, not relied on. It is open, unmerged and labelled needs-user-decision: the domain:spec seat routed its confirmation to the maintainer. This PR adopts its banner phrasing and its Clause-②: no reasoning; both are judged on their own above.
  • Escalated, non-blocking for the verdict: the confirmation path. The gate's own text calls confirmation of a DELIBERATE CORRECTION "the existing human path", and the sibling lane sent the identical correction to the maintainer. This record is the confirmation under this seat's rule and does what that rule requires — it names each changed note by path and judges each rewritten sentence against the cited code. Whether an agent seat's at-tier record stands in for the maintainer's word on this class is a question for the seat and the [finding] the skip-changeset label suppresses the DELIBERATE-CORRECTION refusal whose own text says 「no label and no diff shape makes that safe」 — declared contract against enforced behaviour #18375 rulings, not one this record can settle; the seat lands on it or waits for the maintainer as docs(changeset): correct two scope sentences in the pending service-analytics masked-field note #20991 does. The sentences are right either way.
  • Check-runs on this head, latest run per name, at this reading: 26 names, all concluded — 15 success, 10 skipped, 1 failure, 0 in progress. Green: Check Documentation Links, Dogfood Regression Gate, Governed Surface Queue Guard, Lint & Repo Gates (198 steps, none failed), No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Test Core, The card this PR closes must claim this branch, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, TypeScript Type Check, filter. Skipped: Auto Label, Build Core, Build Docs, Check PR Size, Console Pin Gate, Dogfood Verify CLI, Packed-tarball smoke (opt-in), Temporal Conformance (live PG + MySQL), and the Dogfood and Test Core shard matrices — the diff touches no package source, so the build and test lanes filtered out. The one failure is Check Changeset, the designed red judged in ②: its failing step is the changeset requirement, its annotation names this exact class, and it is the only red on the head. So this PASS is unconditional on every check that judges the diff; the landing rule's "every check green" holds on every check but the one [finding] the skip-changeset label suppresses the DELIBERATE-CORRECTION refusal whose own text says 「no label and no diff shape makes that safe」 — declared contract against enforced behaviour #18375 designs to stay red for a pending-note correction, which this record is written to confirm.

Implemented-by: claude/issue-20917-pending-note-scope
Reviewed-by: session_01XY5uCwTjZj7884yYtyur4H

VERDICT: PASS

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants