@intentius/chant 0.104.4 → 0.105.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/op/activities/factory.d.ts +1 -1
- package/dist/op/activities/factory.d.ts.map +1 -1
- package/dist/workspace/agent-cli.d.ts +14 -8
- package/dist/workspace/agent-cli.d.ts.map +1 -1
- package/dist/workspace/broker-protocol.d.ts +65 -3
- package/dist/workspace/broker-protocol.d.ts.map +1 -1
- package/dist/workspace/conformance/__fixtures__/reference-broker.d.ts.map +1 -1
- package/dist/workspace/conformance/broker.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +9 -2
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +32 -6
- package/dist/workspace/export.d.ts.map +1 -1
- package/dist/workspace/factory-rules.d.ts +9 -4
- package/dist/workspace/factory-rules.d.ts.map +1 -1
- package/dist/workspace/reason-codes.d.ts +1 -1
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/write-scope.d.ts +7 -5
- package/dist/workspace/write-scope.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/mcp/workspace-tools.ts +1 -1
- package/src/op/activities/factory.ts +1 -1
- package/src/op/factory.e2e.test.ts +16 -5
- package/src/workspace/agent-cli.ts +26 -9
- package/src/workspace/agent.schema.json +33 -21
- package/src/workspace/broker-protocol.schema.json +45 -3
- package/src/workspace/broker-protocol.test.ts +45 -0
- package/src/workspace/broker-protocol.ts +131 -5
- package/src/workspace/conformance/__fixtures__/reference-broker.ts +3 -1
- package/src/workspace/conformance/broker.ts +22 -0
- package/src/workspace/declaration.schema.json +32 -6
- package/src/workspace/declaration.ts +29 -15
- package/src/workspace/export.ts +10 -3
- package/src/workspace/factory-rules.test.ts +6 -1
- package/src/workspace/factory-rules.ts +10 -3
- package/src/workspace/reason-codes.ts +1 -1
- package/src/workspace/write-scope.test.ts +18 -1
- package/src/workspace/write-scope.ts +15 -10
|
@@ -119,7 +119,7 @@ export type WriteVerb = (typeof WRITE_VERBS)[number];
|
|
|
119
119
|
|
|
120
120
|
/** One principal class's entry in the declaration's `writeScope` (#2548, ws-067). */
|
|
121
121
|
export interface ClassScope {
|
|
122
|
-
/** The members whose files the class may write, or null for every path. Always null for the agent class: a session writes
|
|
122
|
+
/** The members whose files the class may write, or null for every path. Always null for the agent class: a session writes the members it is bound to. */
|
|
123
123
|
members: string[] | null;
|
|
124
124
|
/** Kind name to the verbs the class may write it with, or null for every kind in reach with every verb. */
|
|
125
125
|
records: Record<string, WriteVerb[]> | null;
|
|
@@ -164,9 +164,16 @@ export interface IdentityPolicy {
|
|
|
164
164
|
gates: Record<string, SignedGate>;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
/**
|
|
167
|
+
/**
|
|
168
|
+
* An agent session the declaration names (#2524 D20, #2548): bound to the
|
|
169
|
+
* members it lists, one or more (ws-101). It writes the files of each, and
|
|
170
|
+
* the records of kinds any of them or the workspace declares.
|
|
171
|
+
*/
|
|
168
172
|
export interface AgentDeclaration {
|
|
169
173
|
name: string;
|
|
174
|
+
/** The members the session is bound to, in file order: `members`, or the one `member` names. Never empty. */
|
|
175
|
+
members: string[];
|
|
176
|
+
/** The first of `members`, kept for readers of a one-member session. */
|
|
170
177
|
member: string;
|
|
171
178
|
/** Principals that write only as this session. */
|
|
172
179
|
principals: string[];
|
|
@@ -952,8 +959,8 @@ function writeScopeOf(raw: unknown, members: Member[], at: (pointer: string, key
|
|
|
952
959
|
|
|
953
960
|
/**
|
|
954
961
|
* The `agents` list, already validated, with the rules the schema can't say
|
|
955
|
-
* (#2548): names are unique,
|
|
956
|
-
* is listed by one session at most.
|
|
962
|
+
* (#2548, ws-101): names are unique, every member a session names is a
|
|
963
|
+
* declared member, and a principal is listed by one session at most.
|
|
957
964
|
*/
|
|
958
965
|
/** The `identity` block, already validated by the schema (#3163). */
|
|
959
966
|
function identityOf(raw: unknown): IdentityPolicy | null {
|
|
@@ -967,23 +974,30 @@ function identityOf(raw: unknown): IdentityPolicy | null {
|
|
|
967
974
|
}
|
|
968
975
|
|
|
969
976
|
function agentsOf(raw: unknown, members: Member[], at: (pointer: string, key?: boolean) => ErrorLocation): AgentDeclaration[] {
|
|
970
|
-
const agents = ((raw as { name: string; member
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
977
|
+
const agents = ((raw as { name: string; member?: string; members?: string[]; principals?: string[] }[] | undefined) ?? []).map((a, i) => {
|
|
978
|
+
// The schema requires exactly one of member and members (ws-101).
|
|
979
|
+
const bound = a.members !== undefined ? [...a.members] : [a.member!];
|
|
980
|
+
return {
|
|
981
|
+
name: a.name,
|
|
982
|
+
members: bound,
|
|
983
|
+
member: bound[0],
|
|
984
|
+
principals: [...(a.principals ?? [])],
|
|
985
|
+
pointer: `/agents/${i}`,
|
|
986
|
+
listed: a.members !== undefined,
|
|
987
|
+
};
|
|
988
|
+
});
|
|
976
989
|
const byName = new Map<string, AgentDeclaration>();
|
|
977
990
|
const byPrincipal = new Map<string, AgentDeclaration>();
|
|
978
|
-
for (const a of agents) {
|
|
991
|
+
for (const { listed, ...a } of agents) {
|
|
979
992
|
const first = byName.get(a.name);
|
|
980
993
|
if (first) throw new WorkspaceReadError("declaration-invalid", `the agent name ${JSON.stringify(a.name)} is already used by the agent at ${first.pointer}`, at(`${a.pointer}/name`));
|
|
981
994
|
byName.set(a.name, a);
|
|
982
|
-
|
|
995
|
+
for (const [i, name] of a.members.entries()) {
|
|
996
|
+
if (members.some((m) => m.name === name)) continue;
|
|
983
997
|
throw new WorkspaceReadError(
|
|
984
998
|
"declaration-invalid",
|
|
985
|
-
`agent ${a.name} is bound to ${JSON.stringify(
|
|
986
|
-
at(`${a.pointer}/member`),
|
|
999
|
+
`agent ${a.name} is bound to ${JSON.stringify(name)}, which is not a declared member; an agent session is bound to members, not an example group`,
|
|
1000
|
+
at(listed ? `${a.pointer}/members/${i}` : `${a.pointer}/member`),
|
|
987
1001
|
);
|
|
988
1002
|
}
|
|
989
1003
|
for (const [i, p] of a.principals.entries()) {
|
|
@@ -992,7 +1006,7 @@ function agentsOf(raw: unknown, members: Member[], at: (pointer: string, key?: b
|
|
|
992
1006
|
byPrincipal.set(p, a);
|
|
993
1007
|
}
|
|
994
1008
|
}
|
|
995
|
-
return
|
|
1009
|
+
return [...byName.values()];
|
|
996
1010
|
}
|
|
997
1011
|
|
|
998
1012
|
/** The `changes` block, already validated, with its defaults (#2773). */
|
package/src/workspace/export.ts
CHANGED
|
@@ -414,9 +414,16 @@ export async function exportWorkspace(o: ExportOptions): Promise<ExportResult> {
|
|
|
414
414
|
return keep.length > 0 ? { ...rest, links: keep } : rest;
|
|
415
415
|
});
|
|
416
416
|
} else if (key === "agents") {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
417
|
+
// A session bound to several members (ws-101) keeps those that go, and is dropped when none does.
|
|
418
|
+
const agents = value as { name: string; member?: string; members?: string[] }[];
|
|
419
|
+
const kept: Record<string, unknown>[] = [];
|
|
420
|
+
for (const a of agents) {
|
|
421
|
+
const bound = a.members ?? (a.member !== undefined ? [a.member] : []);
|
|
422
|
+
const going = bound.filter((m) => names.has(m));
|
|
423
|
+
if (going.length === 0) dropped.agents.push(a.name);
|
|
424
|
+
else kept.push(a.members !== undefined ? { ...a, members: going } : a);
|
|
425
|
+
}
|
|
426
|
+
outDecl.agents = kept;
|
|
420
427
|
} else if (key === "pins") {
|
|
421
428
|
const pins = value as { path?: string; package?: string }[];
|
|
422
429
|
for (const p of pins) if (p.path !== undefined) dropped.pins.push(p.path);
|
|
@@ -8,7 +8,7 @@ import { describe, expect, test } from "vitest";
|
|
|
8
8
|
import { isAsk, pickable, retryState, doneVerdict, proposeImplements, understandOutcome, type FactoryContext, type FactoryItem } from "./factory-rules";
|
|
9
9
|
|
|
10
10
|
describe("the factory's rules", () => {
|
|
11
|
-
const base: FactoryItem = { id: "W-1", state: "open", openState: "open", proposedState: "proposed", ready: true, data: {}, tier: null, contract:
|
|
11
|
+
const base: FactoryItem = { id: "W-1", state: "open", openState: "open", proposedState: "proposed", ready: true, data: {}, tier: null, contract: { id: "C-1", state: "approved" }, warnings: [], answers: [], leased: false };
|
|
12
12
|
const ctx: FactoryContext = { intentDecided: null, claims: [], attempts: { exhausted: false }, branch: null, questions: { tier: null, understand: null } };
|
|
13
13
|
const hold = (i: Partial<FactoryItem>, c: Partial<FactoryContext> = {}) => {
|
|
14
14
|
const v = pickable({ ...base, ...i }, { ...ctx, ...c });
|
|
@@ -19,6 +19,11 @@ describe("the factory's rules", () => {
|
|
|
19
19
|
expect(hold({})).toBe("ok");
|
|
20
20
|
expect(hold({}, { intentDecided: false })).toBe("intent-undecided");
|
|
21
21
|
expect(hold({ ready: false })).toBe("not-ready");
|
|
22
|
+
// Only an item that names a contract, an ask or an intent build is the factory's (#3503).
|
|
23
|
+
expect(hold({ contract: null })).toBe("no-contract");
|
|
24
|
+
expect(hold({ contract: null, data: { source: { kind: "finding", finding: "F-1" } } })).toBe("no-contract");
|
|
25
|
+
expect(hold({ contract: null, data: { source: { ask: { said: "x", by: "a" } } } })).toBe("ok");
|
|
26
|
+
expect(hold({ contract: null, data: { source: { intent: { answer: "x" } } } })).toBe("ok");
|
|
22
27
|
expect(hold({ ready: false, state: "proposed", data: { source: { ask: { said: "x", by: "a" } } } })).toBe("ok");
|
|
23
28
|
expect(hold({ contract: { id: "C-1", state: "draft" }, warnings: ["work-contract-undecided"] })).toBe("contract-not-approved");
|
|
24
29
|
expect(hold({}, { branch: { state: "done", applied: false } })).toBe("built-not-applied");
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
* reference Op (`../op/factory.ts`) runs them; an orchestrator supplies only
|
|
5
5
|
* execution (the builder, the context, the check).
|
|
6
6
|
*
|
|
7
|
-
* - Readiness ({@link pickable}):
|
|
8
|
-
*
|
|
7
|
+
* - Readiness ({@link pickable}): the item names a contract, or is an ask or
|
|
8
|
+
* the box's first build from its intent ({@link isAsk}); a work item a
|
|
9
|
+
* person wrote by hand or a finding's triage item stays with its author
|
|
10
|
+
* (#3503). On top of the work kind's `ready`, the box's intent is decided,
|
|
11
|
+
* a contract the item builds is approved and in force,
|
|
9
12
|
* the item is under its attempt limit, it is not done and waiting to be
|
|
10
13
|
* applied on its branch, its slice-tier and understand questions are not
|
|
11
14
|
* open, an understand answer of redraft or ask holds it, and a failed last
|
|
@@ -37,6 +40,7 @@ export type UnderstandAnswer = (typeof UNDERSTAND_ANSWERS)[number];
|
|
|
37
40
|
/** Why an item is not picked. Closed. */
|
|
38
41
|
export const FACTORY_HOLDS = [
|
|
39
42
|
"not-ready",
|
|
43
|
+
"no-contract",
|
|
40
44
|
"intent-undecided",
|
|
41
45
|
"contract-not-approved",
|
|
42
46
|
"attempts-exhausted",
|
|
@@ -133,7 +137,9 @@ export function understandOutcome(answer: unknown): "dropped" | "redraft" | "ask
|
|
|
133
137
|
const OPEN = new Set(["escalated", "proposed"]);
|
|
134
138
|
|
|
135
139
|
/**
|
|
136
|
-
* Whether the factory picks `item` now, or why not.
|
|
140
|
+
* Whether the factory picks `item` now, or why not. The factory builds an
|
|
141
|
+
* item that names a contract, an ask, or an intent build, as studio's queue
|
|
142
|
+
* did (#3503); any other item is its author's. Asks are candidates while
|
|
137
143
|
* proposed as well as open; any other item only when the work kind reads it
|
|
138
144
|
* ready.
|
|
139
145
|
*/
|
|
@@ -142,6 +148,7 @@ export function pickable(item: FactoryItem, ctx: FactoryContext): { ok: true } |
|
|
|
142
148
|
if (ctx.intentDecided === false) return no("intent-undecided", "the box's intent is not decided yet, so nothing is picked");
|
|
143
149
|
if (item.leased) return no("leased", `${item.id} is leased`);
|
|
144
150
|
const ask = isAsk(item.data);
|
|
151
|
+
if (!item.contract && !ask) return no("no-contract", `${item.id} names no contract and is not an ask or an intent build, so it is left to its author`);
|
|
145
152
|
const candidate = item.ready || (ask && item.proposedState !== null && item.state === item.proposedState);
|
|
146
153
|
if (!candidate) return no("not-ready", `${item.id} is ${item.state ?? "in no state"} and not ready`);
|
|
147
154
|
if (item.contract && (item.warnings.includes("work-contract-undecided") || item.warnings.includes("work-contract-unknown"))) {
|
|
@@ -141,7 +141,7 @@ export const REASONS = {
|
|
|
141
141
|
"write-lock-timeout": "Another write held the working tree's write lock for longer than the write waits (CHANT_WRITE_LOCK_WAIT_MS); the refusal names the holder. Run the write again.",
|
|
142
142
|
"write-lock-not-held": "CHANT_WRITE_LOCK names a batch's lock token that no longer holds the working tree's write lock: the batch released it, or it expired and another writer took it.",
|
|
143
143
|
// Write scope (#2548): a records write that is refused, and a commit check --changes reports, outside the writer's scope.
|
|
144
|
-
"write-scope-member": "The write is to a file, or to a record kind, of a member outside the writer's scope: an agent session writes only
|
|
144
|
+
"write-scope-member": "The write is to a file, or to a record kind, of a member outside the writer's scope: an agent session writes only the members it is bound to, and writeScope.<class>.members leaves the member out.",
|
|
145
145
|
"write-scope-kind": "The write is to a record kind writeScope.<class>.records does not list, with a verb it does not list for the kind, or deletes a record.",
|
|
146
146
|
"write-scope-protected": "The write is to a file writeScope.<class>.protected lists, or one under a directory it lists, outside the top-level keys or JSON Pointers the entry's except allows (#3146, #3308).",
|
|
147
147
|
"write-scope-class-unknown": "The declaration's writeScope at base names a principal class no pinned package supplies, and the writer is judged human, so it may be in that class; the write is refused until the package is installed at the pinned version or the entry removed (#3080).",
|
|
@@ -127,6 +127,9 @@ describe("the declaration's writeScope and agents (#2548)", () => {
|
|
|
127
127
|
["a principal two sessions list", declaration(SCOPE, [{ name: "a", member: "app", principals: ["p"] }, { name: "b", member: "design", principals: ["p"] }]), /already listed by agent a/],
|
|
128
128
|
["two sessions with one name", declaration(SCOPE, [{ name: "a", member: "app" }, { name: "a", member: "design" }]), /already used/],
|
|
129
129
|
["a members list on the agent class", declaration({ agent: { members: ["app"] } }), /members/],
|
|
130
|
+
["an agent naming an undeclared member among several", declaration(SCOPE, [{ name: "a", members: ["app", "nowhere"] }]), /"nowhere", which is not a declared member/],
|
|
131
|
+
["an agent with both member and members", declaration(SCOPE, [{ name: "a", member: "app", members: ["design"] }]), /oneOf|exactly one|must match/],
|
|
132
|
+
["an agent with an empty members list", declaration(SCOPE, [{ name: "a", members: [] }]), /fewer than 1|minItems|members/],
|
|
130
133
|
["an unknown verb", declaration({ agent: { records: { decision: ["delete"] } } }), /must be equal to one of the allowed values|delete/],
|
|
131
134
|
])("refuse %s", (_, text, message) => {
|
|
132
135
|
let err: unknown;
|
|
@@ -210,6 +213,19 @@ describe("judging a write", () => {
|
|
|
210
213
|
expect(judgeRecord(decl, human, screen, "delete")).toEqual({ ok: true });
|
|
211
214
|
});
|
|
212
215
|
|
|
216
|
+
test("an agent bound to several members writes in any of them, and its scope is their union (ws-101)", () => {
|
|
217
|
+
const d = parseDeclaration(declaration(SCOPE, [...AGENTS, { name: "factory", members: ["app", "design"] }]), "chant.workspace.json");
|
|
218
|
+
expect(d.agents.find((a) => a.name === "factory")).toMatchObject({ members: ["app", "design"], member: "app" });
|
|
219
|
+
expect(d.agents.find((a) => a.name === "app-agent")).toMatchObject({ members: ["app"], member: "app" });
|
|
220
|
+
const factory = resolveWriter(d, policy, { agent: "factory" });
|
|
221
|
+
expect(judgePath(d, factory, "app/server.mjs")).toEqual({ ok: true });
|
|
222
|
+
expect(judgePath(d, factory, "design/spec.md")).toEqual({ ok: true });
|
|
223
|
+
expect(judgePath(d, factory, "chant.workspace.json")).toMatchObject({ ok: false, code: "write-scope-member", message: expect.stringContaining("agent session factory, bound to members app, design,") });
|
|
224
|
+
expect(judgeRecord(d, factory, screen, "new")).toEqual({ ok: true });
|
|
225
|
+
expect(judgeRecord(d, factory, ws, "new")).toEqual({ ok: true });
|
|
226
|
+
expect(judgeRecord(d, factory, screen, "amend")).toMatchObject({ ok: false, code: "write-scope-kind" });
|
|
227
|
+
});
|
|
228
|
+
|
|
213
229
|
test("the declared name of a kind is a key for its records rule too", () => {
|
|
214
230
|
const d = parseDeclaration(declaration({ agent: { records: { screen: ["new"] } } }), "chant.workspace.json");
|
|
215
231
|
const design = resolveWriter(d, policy, { agent: "design-agent" });
|
|
@@ -335,7 +351,8 @@ describe("chant workspace agent: what a session reloads from (#2548)", () => {
|
|
|
335
351
|
agent.expectValid(doc);
|
|
336
352
|
if ("error" in doc) throw new Error(doc.error.message);
|
|
337
353
|
expect(doc.workspace).toEqual({ name: "studio", root: ".", scopeFrom: "base" });
|
|
338
|
-
|
|
354
|
+
const design = { name: "design", dir: "design", kind: "other" };
|
|
355
|
+
expect(doc.agent).toEqual({ name: "design-agent", member: design, members: [design], principals: [] });
|
|
339
356
|
expect(doc.scope).toEqual({
|
|
340
357
|
members: ["design"],
|
|
341
358
|
records: [
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Write scope per member and record kind, for each principal class, and the
|
|
3
|
-
* agent sessions bound to
|
|
3
|
+
* agent sessions bound to the members they name (#2524 D5, D20; #2548;
|
|
4
|
+
* ws-067, ws-101).
|
|
4
5
|
*
|
|
5
6
|
* The declaration's `writeScope` block gives a restricted class the members
|
|
6
7
|
* whose files it may write and the record kinds it may write, with which
|
|
7
8
|
* verbs (`new`, `amend`, `review`, `close`). A class with no entry is not
|
|
8
9
|
* restricted. The declaration's `agents` list names agent sessions, each
|
|
9
|
-
* bound to one member: a session writes
|
|
10
|
-
* of kinds
|
|
11
|
-
* allows.
|
|
10
|
+
* bound to one member or several (ws-101): a session writes the files of any
|
|
11
|
+
* of its members, and the records of kinds any of them or the workspace
|
|
12
|
+
* declares, as `writeScope.agent` allows. The scope is the union of its
|
|
13
|
+
* members, and `writeScope.agent` can't widen it.
|
|
12
14
|
*
|
|
13
15
|
* Two places apply it, both reading the scope from the base revision so a
|
|
14
16
|
* change can't widen its own scope (#2524 threat model):
|
|
@@ -170,7 +172,7 @@ export function unknownClassVerdict(declaration: Declaration | null, writer: Wri
|
|
|
170
172
|
};
|
|
171
173
|
}
|
|
172
174
|
|
|
173
|
-
/** The scope that applies to `writer`, or null when its class is not restricted. An agent is always restricted to its
|
|
175
|
+
/** The scope that applies to `writer`, or null when its class is not restricted. An agent is always restricted to its members. */
|
|
174
176
|
export function scopeOf(declaration: Declaration | null, writer: Writer): ClassScope | null {
|
|
175
177
|
const entry = declaration?.writeScope?.[writer.class];
|
|
176
178
|
if (writer.class === "agent") return { members: null, records: entry?.records ?? null, protected: entry?.protected ?? [], pointer: entry?.pointer ?? "/agents" };
|
|
@@ -185,14 +187,17 @@ export type RecordVerdict = { ok: true } | { ok: false; code: Exclude<WriteScope
|
|
|
185
187
|
const OK = { ok: true } as const;
|
|
186
188
|
|
|
187
189
|
function who(writer: Writer): string {
|
|
188
|
-
if (writer.agent)
|
|
190
|
+
if (writer.agent) {
|
|
191
|
+
const bound = writer.agent.members;
|
|
192
|
+
return `agent session ${writer.agent.name}, bound to ${bound.length === 1 ? "member" : "members"} ${bound.join(", ")},`;
|
|
193
|
+
}
|
|
189
194
|
const name = writer.principal !== null ? `${writer.principal} (${writer.class})` : `a ${writer.class} writer`;
|
|
190
195
|
return writer.class === "agent" ? `${name}, which no agent session lists,` : name;
|
|
191
196
|
}
|
|
192
197
|
|
|
193
|
-
/** Whether `writer` may write in `member` (null: a path in no member). */
|
|
198
|
+
/** Whether `writer` may write in `member` (null: a path in no member). An agent session may write in any member it is bound to (ws-101). */
|
|
194
199
|
function memberAllowed(writer: Writer, scope: ClassScope, member: string | null): boolean {
|
|
195
|
-
if (writer.class === "agent") return member !== null &&
|
|
200
|
+
if (writer.class === "agent") return member !== null && (writer.agent?.members.includes(member) ?? false);
|
|
196
201
|
return scope.members === null || (member !== null && scope.members.includes(member));
|
|
197
202
|
}
|
|
198
203
|
|
|
@@ -219,7 +224,7 @@ export function judgeRecord(declaration: Declaration | null, writer: Writer, kin
|
|
|
219
224
|
return {
|
|
220
225
|
ok: false,
|
|
221
226
|
code: "write-scope-member",
|
|
222
|
-
message: `${who(writer)} may not write ${kind.name} records, which are ${where}: ${writer.class === "agent" ? "an agent session writes only
|
|
227
|
+
message: `${who(writer)} may not write ${kind.name} records, which are ${where}: ${writer.class === "agent" ? "an agent session writes only the members it is bound to" : `writeScope.${writer.class}.members leaves it out`}`,
|
|
223
228
|
};
|
|
224
229
|
}
|
|
225
230
|
if (verb === "delete") {
|
|
@@ -319,7 +324,7 @@ export function judgePath(declaration: Declaration | null, writer: Writer, path:
|
|
|
319
324
|
return {
|
|
320
325
|
ok: false,
|
|
321
326
|
code: "write-scope-member",
|
|
322
|
-
message: `${who(writer)} may not write ${path}, which ${where}: ${writer.class === "agent" ? "an agent session writes only
|
|
327
|
+
message: `${who(writer)} may not write ${path}, which ${where}: ${writer.class === "agent" ? "an agent session writes only the members it is bound to" : `writeScope.${writer.class}.members leaves it out`}`,
|
|
323
328
|
};
|
|
324
329
|
}
|
|
325
330
|
const guarded = protectedEntry(scope.protected, path);
|