feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825) - #15029

Merged
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema
Sep 3, 2026
Merged

feat(spec): type KnowledgeRefreshPolicy.cron with CronExpressionInputSchema — the describe promises what the parse enforces (#14825)#15029
zhuangjianguo merged 2 commits into
mainfrom
claude/issue-14825-knowledge-source-cron-schema

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Fixes#14825

Clause ②: yes — needs:contract-review (dual carrier with the card): values that parse green today stop parsing (an empty string), and the parsed shape of a published key changes (bare string becomes the { dialect: 'cron', source } envelope).

What

KnowledgeRefreshPolicySchema.cron (refresh.cron on a knowledge source) was a bare z.string() under a doc comment promising a 5-field cron — a constraint nothing checked (ADR-0049 declared vs enforced). It now carries CronExpressionInputSchema, the shared cron-dialect input the spec's other cron-shaped fields already use, with a describe that promises exactly what the parse enforces. KnowledgeRefreshPolicyParsed / KnowledgeSourceParsed name the parsed state (ADR-0122); the two isomorphism pins they replace leave the registry (831 to 829, receipt in the pin file). New pin file packages/spec/src/ai/knowledge-source.test.ts (8 cases). Changeset @objectstack/spec minor with the BREAKING banner and the ADR-0087 disposition not-required (no-migration-prescription).

Measurement first (triage 5520834102 — the fork is decided by M1, not by preference)

  • M1 — writers of refresh.cron.git grep -nE '\bcron\b' over examples/**, packages/platform-objects/**, packages/qa/**, packages/**/*.test.ts, packages/services/service-knowledge/**, skills/**, content/docs/**, packages/spec/src/ai/** at base 6392b9c2: 557 hits; positive control hit (knowledge-source.zod.ts:33). Every hit read: zero writers of a cron value on a knowledge-source refresh block. The only references to the key are the schema line, the generated reference row, and skills/objectstack-ai/SKILL.md:232 (describes the key, writes no value — still true after typing, untouched). service-knowledge reads only refresh.onRecordChange; ../objectui has zero hits for refresh.cron / KnowledgeRefreshPolicy. Result: TYPE IT.
  • M2 — the dialect's refusal shape (zod 4.4.3, through z.object({ cron: CronExpressionInputSchema.optional() })): '0 9 * * 1-5', the 6-field '0 0 9 * * *' AND 'not a cron' all parse GREEN to { dialect: 'cron', source }; '', 42, null and { dialect: 'js' } are refused with invalid_union at path ['cron'], message Invalid input (branch errors too_small / invalid_type / invalid_value). So H3 is falsified: the shared dialect validates no cron syntax at parse time — the syntax verdict is the @objectstack/formula cron-engine's (5- or 6-field, or an @ alias) when the expression is evaluated, and nothing in this repo imports cron-parser. The describe and the pins state exactly that (declared = enforced), including a deliberate pin that 'not a cron' normalizes rather than being refused, so a later change to the shared dialect surfaces here.
  • M3 — lint scope row.packages/lint/src/validate-expressions.ts reads an enumerated table of CEL predicate keys and returns early on non-CEL dialects; none of the existing cron sites has a row; page-envelope-audit.ts discovers positions behaviourally. No lint scope row is owed. The other site registry — the ADR-0058 D7 expression conformance ledger in packages/qa/dogfood — discovers by schema name and lists only ExpressionInputSchema / SettingsVisibilityInputSchema: its regex replicated verbatim against this tree discovers 24 surfaces, the new site is NOT among them (control: an ExpressionInputSchema slot matches), and none of the 8 existing cron sites is either. Filed as ADR-0058 D7 expression conformance ledger discovers only ExpressionInputSchema / SettingsVisibilityInputSchema positions — the 8 CronExpressionInputSchema and 3 TemplateExpressionInputSchema sites sit outside the ratchet, unclassified #15027 (out of scope: it touches the dogfood ledger and [finding] skills/objectstack-ai/SKILL.md:405-406 calls a model-registry promptTemplate.system / .user "a CEL predicate" — those keys are the template dialect ({{var}}), and the AI domain has no CEL site at all #14797's prompt-template sites). Correction to the card: the enforce half ships at 8 positions in 7 files, not 3 (system/cache, system/disaster-recovery x2, system/job as well).

PM mechanism hypotheses

  • H1 (artifacts):check:generated --fix regenerated the 3 it proved stale (references doc, api-surface, export-origins — the two new XParsed exports). gen:schema moved authorable-defaults/ai.json: ai/KnowledgeSource:refresh {} → (none). The runtime default is unchanged — measured: KnowledgeSourceSchema.parse on a source that omits refresh yields refresh: {} at base and head — but ai/KnowledgeSource now publishes as the input shape and zod's input-mode projection drops the default keyword for a .default() whose object holds a transform (isolated repro: the same wrapper around a transform-free object keeps it). Declared in DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction (its reason is printed by every build), and the refresh describe states the materialized default in words — the data/Field:unique precedent. authorable-surface unchanged (the key already existed); no skills/** path moved; baseRev note: the anchor lags, informational.
  • H2 (changeset level): no gate classifies the level; declared BREAKING (accept-set narrowing plus parsed-shape change) as minor under the launch-window convention. check-changeset-no-major green, check-adr-0087-registration green with not-required (no-migration-prescription) (no key removed/renamed/re-homed, bare strings keep parsing byte-identically, nothing for objectstack migrate meta to rewrite; the one newly refused input never named a schedule), check-empty-changeset green.
  • H3: falsified by M2 — pins assert the measured refusal shape and the measured non-refusal.
  • H4: the describe keeps the producer/consumer sentence and states the enforced dialect.

Verification (final head 242a7be4)

  • vitest: src/ai/knowledge-source.test.ts + src/type-alias-convention.pin.test.ts — 2 files, 11 tests passed.
  • pnpm --filter @objectstack/spec build — VERDICT command-exit 0; typecheck (tsc + scripts + test layer) — VERDICT command-exit 0; tsc -p tsconfig.test.json --listFiles lists the new test file, 0 errors on it.
  • check:generated (no fix) at 242a7be4 after the rebuild: "All 15 generated artifacts are up to date."
  • Reverse verification (fix committed first; mutation cron: z.string().optional() proven on disk by grep counts, 0/1; direction predicted RED on the new file): 7 of 8 cases red, pin file green; restore via git checkout HEAD -- path, git diff HEAD empty, blob hash equals HEAD's blob. The subject resolves from src, so there is no dist leg to this ablation.
  • Gate family derived with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on the actual diff (77 commands): every measured gate green. NOT MEASURED (exit 3 prerequisite / unbuilt closure, CI's farm owns them): check:doc-formula-expressions, check:doc-security-posture, check:type-check-debt, check:dual-build-cjs-loads, check:test-completeness, check-dev-prereqs (66 unbuilt packages).

Not touched

packages/spec/src/ai/model-registry.zod.ts (#14797's territory), packages/spec/src/shared/expression.zod.ts, skills/**, packages/lint. origin/main moved 3 commits during the run (none touch these files); no merge taken, the queue rebuilds on current main.

Out of scope, filed

🤖 Generated with Claude Code

https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i

Generated by Claude Code


Generated by Claude Code

…putSchema` (#14825)
`refresh.cron` on a knowledge source was a bare `z.string()` under a doc
comment promising a 5-field cron — a constraint nothing checked. It now
carries the shared cron-dialect input the three sibling cron fields use,
with a describe that promises exactly what the parse enforces (a non-empty
string or an expression envelope, normalized to the envelope; syntax is the
cron engine's verdict at evaluate time). `KnowledgeRefreshPolicyParsed` /
`KnowledgeSourceParsed` name the parsed state (ADR-0122) and the two
isomorphism pins they replace leave the registry. Pins measured, not
assumed: `'not a cron'` normalizes rather than being refused.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
…re the projection-only default move (#14825)
`check:generated --fix` regenerated the three artifacts it proved stale
(references doc, api-surface, export-origins — the two new `XParsed`
exports). `gen:schema` moved `authorable-defaults/ai.json`: the runtime
default of `refresh` is unchanged (a source omitting it still parses to
`{}`, measured at base and head), but `ai/KnowledgeSource` now publishes
as the input shape and zod's input-mode projection carries no `default`
keyword for a `.default()` whose object holds a transform — declared in
DEFAULT_CHANGES_BY_MAJOR per the gate's own instruction, and the `refresh`
description states the materialized default in words.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174WZTU6XcFcS7g2kykC53i
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 4 documentable anchor(s). ⚠️3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/protocol/knowledge.mdx(via KnowledgeSourceSchema (symbol, a top-level const))
What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/api-surface/ai.json, packages/spec/authorable-defaults/ai.json, packages/spec/export-origins/ai.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 47 of 219 client-bound route-ledger rows — the other 172 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 172: 14 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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.

Coarse fallback — 128 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 a4e4d2d78a42c0bd5602d55afc7089a763f76185packageMentionDocs.

Which tree this was computed on

This run read content/docs from 356639891d7afeb80d8dde2fa789583a549efb43 — the merge of head 242a7be41f529d7ec86d83d454eb85cc255b0baf into base a4e4d2d78a42c0bd5602d55afc7089a763f76185, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 356639891d7afeb80d8dde2fa789583a549efb43 && git checkout 356639891d7afeb80d8dde2fa789583a549efb43
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a4e4d2d78a42c0bd5602d55afc7089a763f76185 242a7be41f529d7ec86d83d454eb85cc255b0baf && git checkout -B drift-repro a4e4d2d78a42c0bd5602d55afc7089a763f76185 && git merge --no-ff 242a7be41f529d7ec86d83d454eb85cc255b0baf
node scripts/docs-audit/affected-docs.mjs --json a4e4d2d78a42c0bd5602d55afc7089a763f76185

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs a4e4d2d78a42c0bd5602d55afc7089a763f76185 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@zhuangjianguoClaude

Copy link
Copy Markdown
CollaboratorAuthor

Landing provenance (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:22Z) — flipping to ready and enabling auto-merge (squash) under the 2026-08-31 clear-is-land ruling.

  • Contract review: in-seat at CONTRACT_REVIEW_TIER (fuse read 17:57Z), PASS · ACCEPT on the card — [finding] KnowledgeSourceSchema.cron is documented as a 5-field cron expression but typed z.string() — the spec's own CronExpressionInputSchema is not used, so 'not a cron' parses green #14825 comment 5529960692; needs:contract-review cleared on both carriers with compared readbacks (5529969357).
  • CI on head 242a7be4: 41 check runs, 34 success + 7 skipped, 0 red, 0 in progress (18:21Z) — including Lint & Repo Gates and TypeScript Type Check at success.
  • Governed-surface test on all nine changed paths: NOT governed (17:57Z) — queue landing applies. check-clause2-carriers.mjs --pair 15029 exit 0 (17:57Z).
  • mergeable_state: clean (18:21Z). Body first line Fixes #14825; no other closing keyword in the body.
  • Docs Drift Check row: content/docs/protocol/knowledge.mdx names KnowledgeSourceSchema; re-read on origin/main (18:21Z) — its three cron/refresh mentions describe cron jobs generically and refresh.onRecordChange, none states the shape of refresh.cron, so nothing on that page is falsified by the typed slot. The three artifact files the drift run could not anchor were reviewed in the diff directly.
  • Next reading: the added_to_merge_queue timeline event; on MERGED the card closes via Fixes, pm:dispatched is stripped in the same action, and the typed slot is probed on origin/main.

Generated by Claude Code

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

Labels

documentationImprovements or additions to documentationprotocol:aisize/mteststooling

Projects

None yet

2 participants

@zhuangjianguo@claude