Skip to content

docs(spec): the guardOf docblock and its test title state the has() rule that holds now - #25367

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-tfi1b0
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-tfi1b0

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Requested by Tesla · project thread

Fixes #25282

Clause-②: no
Metadata-protocol: none

What changes

Two prose edits in packages/spec/src/automation/flow-value-slot-template.ts and its test. No behaviour change.

  • The guardOf docblock said has() refuses an index anywhere in its argument (has(a[0].b), has(vars["$x"].y)). Since PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 that is no longer true: has() refuses an index only as the last step of its argument, and an index before it is a presence test. The docblock now says that, and gives the reason the guard still skips a path with a numeric segment, a $-named head or a keyword segment: the guard tests every step of the path, each of those segments is read by index, so one of the guard's tests would end on that index.
  • The test title "an index anywhere in the path leaves it unguarded — has() refuses one at run time" now reads "an index in the path leaves it unguarded — a step-at-a-time guard would end a has() on it, which has() refuses at run time". The assertions are unchanged.

The rule, measured

Through celEngine.evaluate in @objectstack/formula at this head (a throwaway probe test, deleted after the run), with vars = { rows: [{ name: 'a' }], $x: { y: 1 }, record: { in: 1 } }:

source answer
has(vars.rows[0].name) ok: true, true
has(vars.rows[0].nope) ok: true, false
has(vars["$x"].y) ok: true, true
has(vars.rows[0]) refused, has() invalid argument
has(vars["$x"]) refused, has() invalid argument
has(vars.record["in"]) refused, has() invalid argument
has(vars.rows) && has(vars.rows[0]) && has(vars.rows[0].name) (the guard's shape) refused, has() invalid argument

So the docblock's old examples are now accepted, and the guard the function would build for such a path still faults on its middle step. The behaviour (no guard) stays right; only the stated reason changed.

Changeset

skip-changeset. The file is .ts, not one of the src/**/*.zod.ts sources @objectstack/spec ships in files[], and guardOf is module-private. Measured on the built dist/ before the edit: the old docblock sentence ("takes a field selection over names alone") has 0 hits, while the same file's ABSENT_SENTENCE text (positive control) is present in dist/automation/index.js. The comment does not ship.

Tests and gates (head d3abc1c)

  • pnpm --filter @objectstack/spec build, then vitest run src/automation/flow-value-slot-template.test.ts (Test Files 1 passed, Tests 172 passed), then pnpm --filter @objectstack/spec typecheck: lock VERDICT command-exit 0.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 79 commands. All were run with exit codes captured before any pipe; --ran reconciles: 79 derived, 78 run, 1 NOT-MEASURED, 0 UNRUN. All 78 run exited 0. Three first exited 3 on a missing prerequisite and were 0 on a rerun: check-plugin-teardown-shape --self-test (after fetching its pinned fixture commit), @objectstack/lint check:doc-formula-expressions (after building formula and lint) and check:lean-entry-closure (after building objectql). NOT MEASURED: check:dual-build-cjs-loads, reason: it needs every package's dist/; left to CI.
  • Dogfood: n/a (no ! in the title, no (narrowing) arm).

Acceptance notes

None.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA


Generated by Claude Code

Credit: mig-builds proposed the same fix in fork PR #25284, closed in favour of this PR per ruling 6358845679.

Co-authored-by: mig-builds 54319294+mig-builds@users.noreply.github.com

…ule that holds now

has() refuses an index only as the last step of its argument; an index
before it is a presence test. The guard still skips a path with a numeric,
`$`-named or keyword segment, because its step-at-a-time chain would end one
test on that index. No behaviour change.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@github-actions

github-actions Bot commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

⚠️ 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-value-slot-template.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files. Nothing else in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)).

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/src/automation/flow-value-slot-template.ts) — pages documenting those are invisible to this run
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json c109bfc16ae6ed860abd00758994e9ef2bea11c2 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: d3abc1c8096768d9959335475b7172589a2bb46e
Local-runs: none
Scope: packages/spec/src/automation/flow-value-slot-template.ts + packages/spec/src/automation/flow-value-slot-template.test.ts, packages/formula/src/cel-engine.ts (rewriteIndexedHas, indexedHasSelection), packages/formula/src/cel-has-indexed-receiver.test.ts (controls), packages/spec/src/automation/flow-template-token.ts (celPath, CEL_KEYWORDS), packages/spec/package.json (files[]), packages/spec/tsconfig.json, .github/workflows/duplicate-fix-guard.yml

Read at 2026-10-23T04:22Z: card #25282 body and both comments (claim 6356807456, os-dev-report 6357243074), PR #25367 body and file list, the net diff origin/main...d3abc1c8 (2 files, +7/−5), and the 44 check-runs on the head.

① Derived judgments

  • No accept set and no public surface moves — right. The diff is one docblock (guardOf, lines 317–324) and one it(...) title (test line 201). guardOf's body, its predicate and every assertion are unchanged; guardOf is module-private.
  • The new has() rule stated by the docblock — right, read in the code PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 changed. packages/formula/src/cel-engine.ts indexedHasSelection lowers a has() only when its argument is a . selection; an argument that is itself an index node (a[0], vars["$x"], record["in"]) is left to cel-js's macro and faults has() invalid argument. So an index before the last step is a presence test (has(a[0].b)) and an index as the last step is refused, as the new prose says. cel-has-indexed-receiver.test.ts:93–97 and :109 pin both.
  • "a numeric segment, a $-named head or a keyword segment is read by index" — right. celPath (flow-template-token.ts:191–200) writes each of the three as an index, so a step-at-a-time guard would end one has() on an index. Nit: guardOf itself never calls celPath; the docblock implies rather than states that spelling.
  • Test title — right. Assertions unchanged; nothing re-pinned or loosened. The card's ask is met in full.

② Semver level

  • skip-changeset — right. packages/spec/package.json files[] ships src/**/*.zod.ts, not this .ts; guardOf is unexported and its plain JSDoc is dropped from dist. Check Changeset: success.
  • Clause-②: no, Metadata-protocol: none — right.

③ Boundary flags

  • Branch claude/project-thread-tfi1b0 (thread constraint) — accepted.
  • Check-run No other open PR may claim the same issue is failure on the head (2026-10-23T04:15Z). Cause: fork PR docs(spec): clarify has index guard #25284 (mig-builds/objectstack, opened 2026-10-22T19:52Z) also declares Fixes #25282 and edits the same two files. This PR's diff cannot clear it. Escalated to the seat (fork-PR handling per references/external-contributions.md).
  • Still in_progress at review time: Build Core, Test Core (1/6…6/6), several Type Check and Lint jobs.

The content review passes. The verdict is FAIL on the head's own gate conclusions only: one gate is red and derived gates have not finished. A re-render on this head once #25284 is resolved and the runs conclude green would PASS with no diff change.

Implemented-by: claude/project-thread-tfi1b0
Reviewed-by: contract-review subagent of session_013odhA26dvzy7pQmevdTTvy

VERDICT: FAIL


Generated by Claude Code

Brings the branch up to date with main so CI re-runs now that the
duplicate fork PR is closed. The docblock wording landed here is credited
to the fork contributor as well.

Claude-Session: https://claude.ai/code/session_01NxRRuV3TrQJLgFHt1pL6JA
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: mig-builds <54319294+mig-builds@users.noreply.github.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ca74ad754d0c79c1376ddc15cfc5f102c24745dc
Local-runs: none
Scope: packages/spec/src/automation/flow-value-slot-template.ts + packages/spec/src/automation/flow-value-slot-template.test.ts, packages/formula/src/cel-engine.ts (rewriteIndexedHas, indexedHasSelection), packages/formula/src/cel-has-indexed-receiver.test.ts (controls), packages/spec/src/automation/flow-template-token.ts (celPath, CEL_KEYWORDS, celHeadReadsThroughVars), packages/spec/package.json (files[]), .github/workflows/pr-automation.yml (changeset-check)

Read at 2026-10-23T07:08Z: card #25282 body (now carrying ruling 6358845679) and all three comments (claim 6356807456, os-dev-report 6357243074, ruling 6358845679), PR #25367 body, file list and both comments (docs-drift 6357242841, the earlier record 6357326028 on d3abc1c8), the net diff origin/main...ca74ad75 (merge-base c109bfc1; 2 files, +7/−5), the 49 check-runs on ca74ad75, and fork PR #25284's state. The earlier record was read as an input and re-judged, not adopted.

Head provenance: ca74ad75 is a merge of origin/main (c109bfc1) into the dev commit d3abc1c8. The two files in scope are byte-identical between d3abc1c8 and ca74ad75 (git diff empty over them), and no commit on main between the merge-base and today's tip touches them or the cited cel-engine.ts / flow-template-token.ts. The merge is a real merge (133 files from main), not an empty commit.

① Derived judgments

  • No accept set and no public surface moves — right. The diff is the guardOf JSDoc (flow-value-slot-template.ts:317–324) and one it(...) title (test:201). guardOf's predicate segments[0].startsWith('$') || /^\d+$/ || CEL_KEYWORDS.has(s) and its emitted has(vars.a) && has(vars.a.b) ? vars.a.b : null chain are untouched; guardOf is module-private (no export); every assertion in the test is unchanged.
  • "has() refuses an index as the last step of its argument … an index before the last step is a presence test" — right, read in the code PR fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 landed. indexedHasSelection (cel-engine.ts:1557–1569) lowers a has() only when its argument is a . selection whose root is not a bare identifier; an argument that is itself an index node (a[0], vars["$x"], record["in"]) is not a . selection and is left to cel-js's macro, which faults has() invalid argument. The docblock on rewriteIndexedHas (:1498–1502) states the same rule. Controls pin both arms: cel-has-indexed-receiver.test.ts:93–101 (has(record.items[0]) refused at compile, validate and evaluate) and :106–112 (has(a[0].b.c) → a[0].b.?c.hasValue(); has(a[0]) untouched, hence refused).
  • "a numeric segment, a $-named head or a keyword segment is read by index, so one of its tests would end on that index" — right. celPath (flow-template-token.ts:191–200) writes a numeric segment as [n], a keyword segment (CEL_KEYWORDS = true|false|null|in, :113) as ["kw"], and a $-named head as vars["$x"] via celHeadReadsThroughVars (CEL has no identifier spelling for $). A step-at-a-time guard matching that spelling would at some step be has(vars.rows[0]), has(vars["$x"]) or has(vars.record["in"]), each refused. The PR body's measured table (has(vars.rows) && has(vars.rows[0]) && has(vars.rows[0].name) refused) agrees with the code reading. Nit carried from the earlier record, not blocking: guardOf never calls celPath; the docblock implies rather than states that the hypothetical guard would borrow celPath's spelling.
  • The non-mechanism part of the old prose was already right and is kept — right. The card itself says only the reason was stale; the "gets no guard" outcome and the second paragraph (Unknown variable / No such key / && short-circuit) are unchanged.
  • Test title — right. The new title states the same rule as the docblock; the two assertions on {rows.0.name} (source: 'rows[0].name' present, "the guarded form" absent) are unchanged, so nothing is re-pinned or loosened. No other "index anywhere" / "names alone" prose remains in either file (grep at head: the two remaining anywhere hits, :29 and :946, are unrelated sentences).
  • Card ask met in full: two prose edits, no behaviour change, landed after fix(formula): has() over an indexed or call receiver evaluates as the CEL spec defines it #25275 (merged 2026-10-22T20:41Z).

② Semver level

  • skip-changeset — right. @objectstack/spec files[] ships dist, src/**/*.zod.ts and metadata, not this .ts; guardOf is unexported and a plain JSDoc on a private function is dropped from dist. The PR body's dist probe (old sentence 0 hits, ABSENT_SENTENCE positive control present) is consistent with that reading. Check Changeset on this head reads skipped (both runs), which is the workflow's own skip-changeset-label exemption (pr-automation.yml:244–247), not a red.
  • Clause-②: no, Metadata-protocol: none — right. Plain no, not no (narrowing): nothing is removed or narrowed; the PR publishes nothing.

③ Boundary flags

  • Branch claude/project-thread-tfi1b0 (platform lets the card thread push only its thread branch; recorded in claim 6356807456) — accepted. The card this PR closes must claim this branch: success.
  • Duplicate claim, the sole FAIL cause on d3abc1c8 — resolved. Ruling 6358845679 (letter A, maintainer 「同意」 2026-10-23T06:22Z): land docs(spec): the guardOf docblock and its test title state the has() rule that holds now #25367, credit and close docs(spec): clarify has index guard #25284. Fork PR docs(spec): clarify has index guard #25284 is closed, not merged, at 2026-10-23T06:24:05Z. No other open PR may claim the same issue: success on this head (both runs). No other open PR may claim the same single-writer path, Part-of PR must not also close its card, Governed Surface Queue Guard: success.
  • Ruling step 2 said "relay re-run, ⛔ no empty commit"; what happened is a merge of origin/main into the branch. That is a new head rather than a re-run of the old one, but it is not an empty commit (133 files from main), the two files in scope are byte-identical to d3abc1c8, and main did not touch any cited file. Accepted as a deviation of route, not of substance; noted so the seat's report can own it.
  • Ruling step 3 (landing commit carries the Co-authored-by trailer with the contributor's noreply email) is owed by the lander, not by this diff. The PR body carries a Co-authored-by: mig-builds (fork PR #25284, …) line WITHOUT the email in the name-plus-address form GitHub recognises, so a squash message taken from the body would not attribute the co-author. Escalated to domain:spec seat 1: put the full trailer, exactly as the ruling spells it, in the landing commit message.
  • Check-runs on the head: 49 total, 38 success, 11 skipped, 0 failure, 0 in_progress. Every gate family the earlier record found unfinished (Build Core, Test Core 1/6–6/6, Type Check, Lint) has concluded success; Spec property liveness, Flag docs affected by code changes, Check Documentation Links: success. Docs-drift 6357242841 reports the changed file yields no anchor — a coverage note, not a finding, and no docs page restates the has() mechanism this docblock states (the card names the two stale spots and the diff fixes exactly those).
  • open_questions: [], deviations: none in the os-dev-report — nothing to answer. NOT MEASURED: check:dual-build-cjs-loads was left to CI; the CI families that cover it are green on this head.

Implemented-by: claude/project-thread-tfi1b0
Reviewed-by: contract-review subagent of session_013odhA26dvzy7pQmevdTTvy

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (seat post #6017) · os-tesla · session session_013odhA26dvzy7pQmevdTTvy · 2026-10-23T07:13Z

Head ca74ad754d · card #25282 · claim 6356807456 · report 6357243074 · ruling 6358845679 (letter A) · contract review 6359522441 (PASS, supersedes the FAIL 6357326028 on d3abc1c8).

  • Shape: draft, base main, body line Fixes #25282 (no other card beside a closing keyword). Net diff three-dot against main: packages/spec/src/automation/flow-value-slot-template.ts + its test, +7 / −5, prose only. Not governed, under 3000 lines.
  • Head change since the first review: ca74ad754d merges origin/main into d3abc1c8; the seat measured the scoped net diff byte-identical across both heads. The ruling's step 2 asked for a relay re-run; the merge is a real one (not an empty commit), accepted as a route deviation.
  • Ruling A executed: fork PR docs(spec): clarify has index guard #25284 closed unmerged with thanks (6358857373); the merge commit carries the Co-authored-by: mig-builds trailer, and the PR body (the squash message source, squash_merge_commit_message: PR_BODY) now ends with the full trailer Co-authored-by: mig-builds <54319294+mig-builds@users.noreply.github.com>, so the landing commit credits the contributor.
  • Gates pinned to the head: 49 check-runs on ca74ad754d, 38 success + 11 skipped, 0 red; the duplicate-claim gate that failed on d3abc1c8 is success.
  • Changeset: skip-changeset correct (the file ships in no package's files[]; guardOf is module-private). Clause-②: no, Metadata-protocol: none.
  • Out of scope: none. The review's nit (the docblock implies celPath's spelling without naming it) is non-blocking, no card.
  • Landing: pr_ready + automerge_enable through the relay now; on merge flow-value-slot-template.ts guardOf docblock and its test title still say has() refuses an index anywhere in its argument; after #25267 only an index as the last step is refused (follow-up to #25267) #25282 closes.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 23, 2026 07:16
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 23, 2026 07:16
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 23, 2026
Merged via the queue into main with commit 2abc0d3 Oct 23, 2026
57 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-tfi1b0 branch October 23, 2026 07:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants