Repository navigation
feat(spec): enable.aiReadable — a system object's author can open its records to the AI read tools, read-only - #32971
Conversation
…a system object A system (sys_*) object's author may declare enable.aiReadable: true to let the AI read tools reach its records; it never opens a write. The parse refuses the key on any non-sys_* object and on the stored-metadata family, and isAiReadExposed is the one reading, judging the family before the key. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czFmroo4m96P6zVdd1
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czFmroo4m96P6zVdd1
…e-arm guidance Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czFmroo4m96P6zVdd1
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czFmroo4m96P6zVdd1
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a37dc3cf8bd68205af7540699b528a1b62211306 && git checkout a37dc3cf8bd68205af7540699b528a1b62211306
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 25ee507651f7bae209771fb31661e6edf46f4c8f 9782f9e7fcddd21db4d02371afd79fa8453d2cf9 && git checkout -B drift-repro 25ee507651f7bae209771fb31661e6edf46f4c8f && git merge --no-ff 9782f9e7fcddd21db4d02371afd79fa8453d2cf9
node scripts/docs-audit/affected-docs.mjs --json 25ee507651f7bae209771fb31661e6edf46f4c8f
|
|
domain:spec#3 · os-elon-musk · session_016WV4d1T5HEokjRgR6vCBX8 Contract reviewServed-tier: Read, not reviewed (outside the diff): ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentOpen question (parse versus a
First consumer. Liveness verdict Findings, all nit (no effect on correctness or the contract at this head):
Implemented-by: card thread session_016WV4czFmroo4m96P6zVdd1 Verdict: PASS Items: no must-fix, no should-fix; five nits above (final round). Owed outside this PR: the |
|
domain:spec#3 · os-elon-musk · session_016WV4d1T5HEokjRgR6vCBX8 席位核对 · ACCEPT · PR #32971 · head
|
Requested by Elon · project thread
Fixes #32865
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #32974 — skills/objectstack-data/SKILL.md (the
enablecapability table has noaiReadablerow; it should say the key is refused on any object whose name does not start withsys_, so an app author never writes it)维护者速读
enable块新增aiReadable(只读的 AI 开放声明)。只有系统表(sys_开头)可以声明,声明后 AI 的读工具(查询、取单条、聚合、描述、列表)可以读它的记录,权限和行级安全照旧;它永远不开放写,写仍只走动作自己的ai.exposed。false会让人误以为锁住了);sys_metadata、sys_metadata_history两张存元数据正文的表一律拒绝。运行时还没有任何地方读它。回滚即 revert 本 PR。packages/spec/src/data/ai-read-exposure.ts(拒收与唯一读法isAiReadExposed,元数据表先于键判断);packages/spec/src/data/object.zod.ts中aiReadable键与写臂提示;packages/spec/src/data/ai-read-exposure.test.ts。pnpm --filter @objectstack/spec exec vitest run src/data/ai-read-exposure.test.ts,应见 17 个测试全绿。What changes
@objectstack/specgains a per-object, read-only AI exposure declaration, executing the maintainer's ruling A on the read half of #32442 (ruling record 6837430865).ObjectCapabilities.aiReadable(enable.aiReadable, optional boolean, absent by default).truedeclares that the AI read tools (query, get one, aggregate, describe, list) may reach this system object's records. Every gate after admission is unchanged: the caller's permissions, row-level security, field masking, and the object's API exposure. It never opens a write; a write on a system object stays on its actions, each opened by its ownai.exposed(ADR-0011). No deployment switch is added, andallowSystemObjectsis untouched.ObjectSchema's existing refinement (refuseMisplacedAiReadExposure→aiReadExposureRefusal), so every door that parses an object inherits it (ObjectSchema.create,defineStack,os validate, the object save door):STORED_METADATA_BODY_OBJECTS(sys_metadata,sys_metadata_history) inpackages/spec/src/kernel/stored-metadata-body-objects.ts, the one list the action surface already refuses by (ADR-0066 D4, note of 2026-10-30). It is imported, not copied;sys_is refused: the MCP record tools already read a business object as the caller, so the key would change nothing there, andfalsewould read as a lock that locks nothing (ADR-0049);falseincluded. The key is new, so no stored object carries it.enabledeclares no other AI key.aiExposed,aiWritable,aiWrite,aiEditableandaiinsideenableare refused with a read-only prescription that points writes at actions'ai.exposed;aiRead,aiReadEnabled,aiReadExposedandmcpReadableget a rename toaiReadable. A value that is not a boolean is refused.isAiReadExposed(schema), exported from@objectstack/spec/data, is the one reading for the server half. It answersfalsefor the stored-metadata family before it reads the key,falseoff asys_*object, andtrueonly for the booleantrue(fail-closed, the shape ofisPublicSharingEnabled).object.enable.aiReadableisplanneduntil the server half lands. The Studio object form omits the key (reconciliation ledger row), because the objects that form edits are the ones the parse refuses it on.Placement and mechanism (measured; for the contract review)
enablerather than a new top-levelaiblock. The MCP layer already reads an object'senableblock for its record tools:packages/mcp/src/stdio-data-bridge.tsgates every data verb onenable.apiEnabled/apiMethods, and both bridges carryenableFeatures: def.enableinto the object description (packages/mcp/src/mcp-server-runtime.ts,packages/runtime/src/domains/mcp.ts). The new key sits beside the exposure flags it is read with, andenablealready holds a read-only opt-in of the same kind (approvalsVisibleToReaders). A top-levelaiblock would add a second home for object exposure, and itsexposedwould read as full exposure.aiReadablekeeps theaiprefix and states the read-only scope in the name. The exact action spelling,aiExposed, is refused with guidance, because on an object it would read as write exposure too.packages/lintrule. The card says the off-sys_*declaration is "refused by lint". A parse refusal is a strict superset:os validatereports parse issues, and the parse also coversdefineStack, module load and the object save door, which a lint rule does not reach. This follows the precedents in the same refinement (refuseWriteLockedUserActions,refuseExternalBindingOnPrimaryDatasource). The claim's file surface was amended accordingly (claim re-post 6839889768). The contract review should confirm this mechanism change.Tests
packages/spec/src/data/ai-read-exposure.test.ts, 17 cases: accepted onsys_approval_request(true, false, absent); refused on a business object (true and false, located atenable.aiReadable); refused on each member ofSTORED_METADATA_BODY_OBJECTS(true and false);isAiReadExposedanswersfalseon each family member without reading the key (getter probe, with a control leg on a non-family system object that is read); no write arm (enabledeclares exactly oneai/mcpkey, the write-arm names areunrecognized_keys, non-boolean values areinvalid_type, the reading opens on booleantrueonly).Ablations (each rule once;
scripts/ablation-replace.mjswith an on-disk anchor proof, EXIT-trap restore, blob == HEAD verified after each; spec tests importsrc/directly, so no dist rebuild applies):sys_*removedisAiReadExposedremovedsys_*removedenableTwo existing tests were updated for the new key:
alias-integrity.test.ts(theenableguidance table now lists the five write-arm names) andmetadata-form-zod-reconciliation.test.ts(anomitledger row forobject.enable.aiReadable).Acceptance notes
isAiReadExposedon the object's registered definition. Whether a tenant object overlay of asys_*object can carryenable.aiReadable(ADR-0029 D9 overlays) is the server half's to measure; this PR measured only that an object extension cannot carryenableat all (ObjectExtensionSchemais closed and takes noenable).sys_predicate (packages/mcp/src/mcp-http-tools.ts,packages/runtime/src/action-execution.ts#isSystemObjectName, and the module-private one here) are not unified; spec cannot import either runtime copy. Noted, not filed.Blocked-by:edge has cleared; the merged key isenable.aiReadableand its reading isisAiReadExposed.Verification
All readings at head
9782f9e7fc(merge oforigin/main2d52e29 into the branch), after a whole-treeturbo run build --concurrency=2(VERDICT command-exit 0).Derived gate families — 113 derived, 113 run, all exit 0
node scripts/pm/dispatch-gates.mjs --ran FILE→113 derived famil(ies) accounted for — 113 run, 0 NOT-MEASURED (a DERIVED zero — all 113 recorded an exit code and none of them is 3). The list covers the@objectstack/specartifact and source audits (check:api-surface,check:authorable-surface,check:docs,check:liveness,check:strictness-ledger,check:export-origins,check:exported-any,check:generated, …), the changeset gates (check-adr-0087-registration,check-changeset-no-major,check-empty-changeset),check:nul-bytes,check:test-source-alias,check:type-check-coverage,check:type-check-debt(under the verify lock) and the rest of the derivation. Families outside the derivation run in CI and are read from this PR's check-runs.Package suites
pnpm --filter @objectstack/spec test→Test Files 837 passed (837),Tests 26754 passed | 1 todo(VERDICT command-exit 0).pnpm --filter @objectstack/spec typecheck→ exit 0.pnpm --filter @objectstack/lint exec vitest run(consumer of the spec contract) →Test Files 200 passed (200),Tests 8579 passed.pnpm --filter @objectstack/spec check:generated→All 14 generated artifacts are up to date.Clause-②:/Metadata-protocol:lines read byscripts/pm/clause2-line.mjsfrom the changeset:yes (widening),added.Not run locally:
pnpm lintandtest:repo(CI's); the dogfood battery (no!in the title, no(narrowing)arm).Human Minimum
packages/specholds no runtime logic (the exported reading is a pure predicate, asisPublicSharingEnabled).pnpm lintis CI's.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code