Repository navigation
Commit 107853c
feat(spec): IAIConversationService.get / list and IAIService.listPendingActions take the caller's execution context (#23197)
Fixes #23158
Clause-②: yes (widening: an optional parameter on three published
store-contract methods)
## What changes
The three AI store reads take the caller's execution context as an
optional trailing parameter. This is the contract change that
objectstack-ai/cloud#2844 Q1 ruling C names (maintainer 「同意」, cloud
comment `6138290535`), in the shape triage's direction gives
(`6139367236`).
| Method | FROM | TO |
|---|---|---|
| `IAIConversationService.get` | `get(conversationId)` |
`get(conversationId, context?: ExecutionContext)` |
| `IAIConversationService.list` | `list(options?)` | `list(options?,
context?: ExecutionContext)` |
| `IAIService.listPendingActions` | `listPendingActions?(filter?)` |
`listPendingActions?(filter?, context?: ExecutionContext)` |
The TSDoc states the rule an implementation follows:
- **Supplied:** the implementation passes it unchanged as the `context`
of every engine read the method makes (`BaseEngineOptions.context`), so
the read runs as that caller. Under ADR-0096 D5 strict mode an engine
read with no principal is refused with 403.
- **Omitted:** the read stays the one the implementation made before
this parameter existed.
- `list`'s `options.userId` is called out as a filter on the owner. It
is not the caller's identity.
- Whether a caller is admitted to the rows is the stored objects' access
and sharing question, which these contracts do not declare.
Files: `packages/spec/src/contracts/ai-service.ts` (three signatures,
their TSDoc, and one type-only import), a pin file beside it, and
`.changeset/23158-ai-store-caller-context.md` (`@objectstack/spec`
`minor`, carrying the `Clause-②` line and the FROM → TO). No generated
artifact moved (`check:generated` green, see below). No object's
`access` or sharing changed.
## Measurements
### H1: "the shape the other store contracts use"
Population at `origin/main` `de24373ec`,
`packages/spec/src/contracts/*.ts` (test files excluded): the methods
that take the caller's context.
| Spelling | Contract: methods (lines) |
|---|---|
| positional `context: ExecutionContext`, required |
`sharing-service.ts`: 15 (`:249`–`:588`) · `approval-service.ts`: 12
(`:881`–`:996`) · `share-link-service.ts`: 3 (`:237`, `:245`, `:256`) |
| positional `context?: ExecutionContext`, optional |
`analytics-service.ts`: 3 (`:312`, `:329`, `:351`) |
| positional `context?: SecurityContext` (a `Partial` of
`ExecutionContext`) | `security-service.ts`: 20 (`:418`–`:1049`) |
| a field in an options bag | `data-engine.ts` `options?:
BaseEngineOptions` (`:289`–`:311`) · `objectql-engine.ts:198` ·
`knowledge-service.ts:43` (`executionContext?`) ·
`automation-service.ts:709` |
The majority is `ExecutionContext` from
`../kernel/execution-context.zod.js`, as a positional parameter after
the method's own arguments: 33 methods across 4 contracts. In 31 of the
33 it is the last parameter, and in the other 2 one optional bag follows
it. `approval-service.ts`'s `listRequests(filter, context)` has the same
shape as `list` here. The parameter is optional, as triage directs and
as `IAnalyticsService` already does. The import specifier carries `.js`,
as 5 of the 7 sibling imports do.
**Conflict, stated rather than resolved silently:** the card body's ask
said "the same shape the engine's read verbs take", which is the
options-bag form. Triage's direction (Zone 1, binding) says "the shape
the other store contracts use", and this PR follows triage. The two
forms meet at the engine call: the type-level pin below proves
`ExecutionContext` is assignable to `BaseEngineOptions['context']`, so
an implementation forwards it with no conversion.
### H2: where the objects live, and the owner-reads-own-rows reading:
NOT MEASURED here
None of the four objects is declared in this repository. On
`origin/main` `de24373ec`, a `git grep` for the declaration spelling
`name: 'OBJECT'` returns nothing for `ai_conversations`, `ai_messages`,
`ai_pending_actions` or `ai_agent_memories`. The only hits are test
fixtures in `plugin-sharing` and name references in the share-link
routes. The control leg is the same grep for `name: 'sys_user'`, which
hits `packages/platform-objects/src/identity/sys-user.object.ts:46`.
This repository's liveness ledger
(`packages/spec/liveness/agent.json:96`) cites the store at `cloud
@ef5a4344: packages/service-ai/src/agent-memory.ts#AgentMemoryStore —
the store, over the object ai_agent_memories (access private, no API)`.
I did not read that file directly.
So triage's measurement ("with a caller threaded through, does an
ordinary member read their own rows on these `access: private`
objects?") cannot run in this repository. It can run in
objectstack-ai/cloud's `service-ai`, with this contract at cloud's pin.
The cloud card that switches the 18 reads back to the caller's identity
is the natural place to measure it. **No sharing card is filed on an
unmeasured premise.** If that measurement shows an owner is refused, the
sharing question is filed then, from there.
### H3: the pins, without an in-repo implementer
`git grep` on `origin/main` finds no implementer of
`IAIConversationService` or of `IAIService.listPendingActions` in this
repository. The only users are `ai-service.test.ts`'s mocks and a type
reference in `packages/runtime/src/domains/ai.ts:67`, which never calls
these methods. So the pins in
`packages/spec/src/contracts/ai-store-caller-context.pin.test.ts` are
contract-level:
- **Type level** (compiled by `check:test-typecheck` under
`tsconfig.test.json`):
- each method's trailing parameter is exactly `ExecutionContext |
undefined`, and nothing follows it (arity `1 | 2` / `0 | 1 | 2`);
- `ExecutionContext` is assignable to the non-null
`BaseEngineOptions['context']`;
- an implementation written against the shorter signatures still
satisfies each method type;
- calls without the context compile.
- **Runtime:** a minimal in-test implementation, over a recording engine
port typed as `IDataEngine`'s `find` / `findOne` (a `Pick`), is written
to the TSDoc's forwarding rule.
- With a member context, every engine read (`get` makes two,
`ai_conversations` then `ai_messages`) carries it as `options.context`,
by reference, and the real `BaseEngineOptionsSchema.safeParse` accepts
those options.
- Without one, each engine read gets no options argument at all, which
is the call the shorter signature made.
- The runtime half pins the rule as executable reference. It cannot pin
`service-ai`, and that implementer's own conformance lives with it.
**Reverse verification (ablation), one per method, from the committed
state** (the HEAD blob of `ai-service.ts` is `84b68834da23`). Each leg
ran through `node scripts/ablation-replace.mjs`: the anchor hit 1 → 0,
the blob changed, and on restore the blob equalled HEAD and `git diff
HEAD` was empty. No `dist/` rebuild was needed, because the pin imports
`./ai-service` and `../data/data-engine.zod` by relative path, so the
program reads `src/`. Measured with the spec test-layer program (`tsc
--noEmit -p tsconfig.test.json`):
| Leg | Pin-file errors | Program total | What went red |
|---|---|---|---|
| baseline (HEAD) | 0 | 246 (the ledger's 246) | none |
| `get` without `context` | 7 | 253 | `GetTakesTheCallerContext` and
`GetArity` (TS2344), TS2493, the in-test `get` (TS2322 / TS7006), the
two-argument call (TS2554) |
| `list` without `context` | 7 | 253 | `ListTakesTheCallerContext` and
`ListArity`, the same classes |
| `listPendingActions` without `context` | 7 | 253 |
`ListPendingActionsTakesTheCallerContext` and `ListPendingActionsArity`,
the same classes |
The observed direction is turning red, at the type layer. The runtime
half stays green under each ablation, because types are erased at run
time. That is expected: the contract is a type, and the type half is
what fails.
### H4: the typecheck debt ledger
The pinned entry `src/contracts/ai-service.test.ts`: `TS2739 … missing …
getTurnState, update` is unmoved. A mock written against the shorter
`get` / `list` stays assignable, so no new signature arrives.
`check:test-typecheck` is green with the ledger untouched: `52 file(s) /
246 error(s) / 135 pinned signature(s) held`. The new pin file compiles
with zero errors. `packages/spec/test-typecheck-debt.json` is not in
this diff.
## Verification
Readings taken at HEAD `c128fd10a`: this branch after merging
`origin/main` `32fa1942e`, which touched `packages/spec` in disjoint
files.
- `pnpm --filter @objectstack/spec build`: `VERDICT command-exit 0`. The
rebuilt `dist/contracts/index.d.ts` carries the three new signatures
(`:5745`, `:6084`, `:6094`).
- `pnpm --filter @objectstack/spec check:generated`: exit 0, `All 14
generated artifacts are up to date`. That includes `check:api-surface`
and `check:export-origins` (no export added or removed) and
`check:test-typecheck`.
- `pnpm --filter @objectstack/spec exec vitest run --project local
--maxWorkers=2 src/contracts/ai-store-caller-context.pin.test.ts
src/contracts/ai-service.test.ts`: `Test Files 2 passed (2)`, `Tests 27
passed (27)`.
- The full spec suite, `pnpm --filter @objectstack/spec exec vitest run
--project local --maxWorkers=2`: `Test Files 662 passed (662)`, `Tests
20114 passed | 1 todo`.
- `pnpm --filter @objectstack/spec typecheck`: exit 0.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` (no paths) derived 85 commands. Each line
was run with its exit code captured before any pipe.
- 82 exited 0 on the first pass.
- 2 exited 3 (PREREQUISITE NOT MET) and went green after their
prerequisites were built. `check:doc-formula-expressions` passed after
`turbo run build --filter=@objectstack/formula
--filter=@objectstack/lint`: `22 record-scoped formula example(s) across
474 files / 1391 TS blocks judged clean`. `check:lean-entry-closure`
passed after building `@objectstack/objectql`: `Admitted set held
exactly (15 packages)`.
- 1 is **NOT MEASURED**: `check:dual-build-cjs-loads`, which needs every
package's `dist/`, so a whole-workspace build. As a declared narrowing,
all 19 `@objectstack/spec` CJS require entries the gate lists (`--list`)
load in a fresh `node` with 0 failures. The diff adds only a type-only
import, which is erased from the emitted JS.
- `--ran` reconciliation: `85 derived famil(ies) accounted for — 84 run,
1 NOT-MEASURED`.
- ESLint, narrowed and proven: `pnpm exec eslint --no-inline-config
--format json` over the two changed `.ts` files reports 2 files, 0
errors and 0 warnings. Both files are in the config's population
(`--print-config` resolves rules for each). The config enables no
type-aware linting (`eslint.config.mjs:327`–`:328`: no
`parserOptions.project`), so this diff cannot move the verdict on any
untouched file. The full `pnpm lint` is CI's run.
- Consumers: none in this repository call the three methods (H3). The
public surface widens by an optional parameter only, so no import-side
test is owed here.
## Acceptance notes
- **The card body's asks 2 and 3 are not in this PR.** Ask 2 (owner
reads of the four objects) and ask 3 (a non-owner refused, the owner
admitted) concern the objects, which are declared in
objectstack-ai/cloud. Triage's direction keeps sharing out of this card
("⛔ no sharing widened as a rider") and routes it to a card filed only
after the measurement in H2. That measurement can only run where the
objects are declared.
- **Other contract reads with no caller context**, noted and not filed.
`IAIConversationService.getTurnState` reads the turn's `ai_messages`,
and `addMessage` / `update` answer with the conversation. None of them
takes a caller context. The ruling and triage name exactly three
methods, so this PR changes those three. Carrier: the cloud card that
switches the 18 reads back, which will see whether any of these is among
the reads D5 refuses.
---
_Generated by [Claude
Code](https://claude.ai/code/session_012uFegdfR6K6uqvLzt7XsdZ)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent c6139f2 commit 107853c
3 files changed
Lines changed: 292 additions & 15 deletions
File tree
- .changeset
- packages/spec/src/contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| 56 | + | |
56 | 57 | | |
57 | 58 | | |
58 | 59 | | |
| |||
443 | 444 | | |
444 | 445 | | |
445 | 446 | | |
446 | | - | |
447 | | - | |
448 | | - | |
449 | | - | |
450 | | - | |
451 | | - | |
452 | | - | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
453 | 471 | | |
454 | 472 | | |
455 | 473 | | |
| |||
749 | 767 | | |
750 | 768 | | |
751 | 769 | | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
| 773 | + | |
| 774 | + | |
| 775 | + | |
| 776 | + | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
| 785 | + | |
| 786 | + | |
| 787 | + | |
| 788 | + | |
752 | 789 | | |
753 | 790 | | |
754 | 791 | | |
| |||
770 | 807 | | |
771 | 808 | | |
772 | 809 | | |
| 810 | + | |
| 811 | + | |
773 | 812 | | |
774 | 813 | | |
775 | | - | |
| 814 | + | |
776 | 815 | | |
777 | 816 | | |
778 | 817 | | |
779 | | - | |
| 818 | + | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
780 | 823 | | |
781 | 824 | | |
782 | | - | |
783 | | - | |
784 | | - | |
785 | | - | |
786 | | - | |
787 | | - | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
788 | 834 | | |
789 | 835 | | |
790 | 836 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
0 commit comments