@things-factory/ai-client-base 10.1.28 → 10.1.31

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.
Files changed (27) hide show
  1. package/dist-server/service/agentic-loop/agentic-loop.js +13 -1
  2. package/dist-server/service/agentic-loop/agentic-loop.js.map +1 -1
  3. package/dist-server/service/assistant/registry-assistant.d.ts +11 -0
  4. package/dist-server/service/assistant/registry-assistant.js +5 -1
  5. package/dist-server/service/assistant/registry-assistant.js.map +1 -1
  6. package/dist-server/service/assistant/tool-scope.d.ts +3 -3
  7. package/dist-server/service/assistant/tool-scope.js +32 -15
  8. package/dist-server/service/assistant/tool-scope.js.map +1 -1
  9. package/dist-server/service/tool-registry/index.d.ts +1 -1
  10. package/dist-server/service/tool-registry/index.js.map +1 -1
  11. package/dist-server/service/tool-registry/registry.d.ts +9 -8
  12. package/dist-server/service/tool-registry/registry.js +26 -38
  13. package/dist-server/service/tool-registry/registry.js.map +1 -1
  14. package/dist-server/service/tool-registry/types.d.ts +16 -31
  15. package/dist-server/service/tool-registry/types.js.map +1 -1
  16. package/dist-server/tsconfig.tsbuildinfo +1 -1
  17. package/package.json +2 -2
  18. package/server/service/agentic-loop/agentic-loop.ts +14 -1
  19. package/server/service/agentic-loop/proposal-key.test.ts +31 -0
  20. package/server/service/assistant/registry-assistant.test.ts +65 -15
  21. package/server/service/assistant/registry-assistant.ts +13 -1
  22. package/server/service/assistant/tool-scope.test.ts +123 -0
  23. package/server/service/assistant/tool-scope.ts +34 -17
  24. package/server/service/tool-registry/index.ts +0 -1
  25. package/server/service/tool-registry/registry.test.ts +34 -66
  26. package/server/service/tool-registry/registry.ts +35 -46
  27. package/server/service/tool-registry/types.ts +16 -33
@@ -32,13 +32,15 @@ const categories: Map<string, ToolCategory> = new Map()
32
32
  /** A registration that was accepted but is not yet what the contract requires. Code + params, not prose. */
33
33
  export interface ToolRegistryWarning {
34
34
  /**
35
- * `tool-privilege-undeclared` — nothing said which door this tool reaches.
36
- * `tool-door-has-no-privilege` — the door was named and it declares no privilege of its own yet
37
- * (twin's queries, waiting on AUTH stage two). The tool cannot be stricter than its door, so
38
- * this is a different fact from saying nothing, and the report keeps the two apart.
35
+ * `tool-doors-undeclared` — nothing said which doors this tool reaches, so the gate leaves it out
36
+ * of every conversation (`resolveToolScope`) and this names it.
37
+ *
38
+ * What those doors require is **not** asked here. Registration runs while modules load, before the
39
+ * schema stands, so AUTH's field table is still empty; `ai-assistant`'s boot report asks the
40
+ * schema after it is built (`tool-door-not-in-schema` · `tool-door-has-no-privilege`).
39
41
  */
40
- code: 'tool-privilege-undeclared' | 'tool-door-has-no-privilege'
41
- params: { category: string; tool: string; kind: ToolSpec['kind']; doors?: string[] }
42
+ code: 'tool-doors-undeclared'
43
+ params: { category: string; tool: string; kind: ToolSpec['kind'] }
42
44
  /** For a person reading it; the code and params are what callers act on. */
43
45
  message: string
44
46
  }
@@ -46,8 +48,8 @@ export interface ToolRegistryWarning {
46
48
  const warnings: ToolRegistryWarning[] = []
47
49
 
48
50
  /**
49
- * What registration accepted but the contract does not yet allow. Stage one of A1 records these
50
- * instead of refusing; a host shows them at boot, and when the list is empty the rule becomes a refusal.
51
+ * What registered without saying which doors it reaches. A host prints this at boot; the gate has
52
+ * already left each of these tools out of every conversation.
51
53
  */
52
54
  export function getToolRegistryWarnings(): ReadonlyArray<ToolRegistryWarning> {
53
55
  return warnings.slice()
@@ -80,53 +82,40 @@ export function registerToolCategory(category: ToolCategory): void {
80
82
  }
81
83
 
82
84
  /*
83
- * A tool that changes something must say which privilege it goes through (A1, 2026-09-17).
85
+ * A tool says which doors it reaches (ADR-0071 decision 2).
84
86
  *
85
- * The conversation door checks one privilege — may this person talk to the assistant. Without a
86
- * privilege on the tool, that single check opened every tool behind it: a user holding only
87
- * `ai-assistant:mutation` could reach board-import's `materializeImportSession` and create a
88
- * Board.
87
+ * The conversation door checks one privilege — may this person talk to the assistant. On its own
88
+ * that single check opened every tool behind it: a user holding only `ai-assistant:mutation` could
89
+ * reach board-import's `materializeImportSession` and create a Board.
89
90
  *
90
- * **This is stage one of two: surface, then refuse** (ADR-0037, "먼저 드러내고, 목록이 비면 막는다").
91
- * Refusing now would stop every app whose tools predate the `privilege` field from booting, and
92
- * those apps compile against an installed copy that does not have the field yet. So an undeclared
93
- * tool still registers, and the omission is recorded as a code (`getToolRegistryWarnings`), not
94
- * a log line — a warning only a log carries is not distinguishable from no warning. When the list
95
- * is empty across the apps, this becomes a refusal.
91
+ * Registration does not refuse a tool that says nothing; it records the omission as a code
92
+ * (`getToolRegistryWarnings`), not a log line — a warning only a log carries is not
93
+ * distinguishable from no warning. The refusal happens where it costs nothing to be late: the gate
94
+ * leaves an undeclared tool out of every conversation, so booting stays possible while the list is
95
+ * worked down, and no undeclared tool is reachable in the meantime.
96
96
  */
97
97
  warnings.splice(0, warnings.length, ...warnings.filter(w => w.params.category !== category.name))
98
- for (const spec of category.specs) {
99
- const declared = Boolean(spec.privilege?.category && spec.privilege?.privilege)
100
- if (declared) continue
101
-
102
- /*
103
- * An empty list says this tool reaches no door — the ones that hand back what the surface sent
104
- * are that. Nothing to gate, nothing to report.
105
- */
106
- if (Array.isArray(spec.doors) && spec.doors.length === 0) continue
107
98
 
108
- if (spec.doors?.length) {
109
- /*
110
- * The door is named and carries no privilege yet. The tool cannot be stricter than the door
111
- * it reaches, so this is not an omission by whoever wrote the tool — it is the door's turn
112
- * (ADR-0071 decision 2). Reported under its own code so the two are not read as one list.
113
- */
114
- warnings.push({
115
- code: 'tool-door-has-no-privilege',
116
- params: { category: category.name, tool: spec.name, kind: spec.kind, doors: spec.doors },
117
- message:
118
- `tool '${spec.name}' reaches ${spec.doors.join(' + ')}, which declare no privilege — ` +
119
- 'the tool is as open as the doors it reaches until they declare one'
120
- })
121
- continue
122
- }
99
+ /*
100
+ * ── 문을 안 적은 도구는 그 답을 **아무도 못 읽게 만든다** ─────────────────
101
+ *
102
+ * ADR-0073 뒤로 어시스턴트의 답 행에는 그 답을 만든 도구들이 지난 문이 적힌다. 선언이 없는 도구는
103
+ * `unresolved:<도구>` 로 적히고 그 이름은 아무도 못 지난다 — **답을 쓴 본인도** 새로 고치면
104
+ * 「이 답을 볼 권한이 없습니다」를 본다. 2026-09-18 트윈 모델러에서 그렇게 나왔다.
105
+ *
106
+ * 2026-09-18 부터 이 선언은 관문이기도 하다(ADR-0071 결정 2 보탬). 문을 안 적은 도구는 어느 대화에도
107
+ * 나가지 않으므로, 이 목록이 「무엇이 빠져 있나」를 말하는 유일한 자리다.
108
+ */
109
+ for (const spec of category.specs) {
110
+ if (Array.isArray(spec.doors)) continue
123
111
 
124
112
  warnings.push({
125
- code: 'tool-privilege-undeclared',
113
+ code: 'tool-doors-undeclared',
126
114
  params: { category: category.name, tool: spec.name, kind: spec.kind },
127
115
  message:
128
- `tool '${spec.name}' in category '${category.name}' says nothing about the doors it reaches — ` +
129
- "declare `doors` with the same { category, privilege } those doors use, or `doors: []` when it reaches none"
116
+ `tool '${spec.name}' in category '${category.name}' declares no doors, so it is offered to no ` +
117
+ 'conversation and answers built with it would be hidden from every reader including their ' +
118
+ 'author — declare `doors`, or `doors: []` when it reaches none'
130
119
  })
131
120
  }
132
121
 
@@ -119,23 +119,6 @@ export interface ExternalToolContext {
119
119
  }
120
120
  // ── ToolSpec — 카테고리별 변형 ──────────────────────────────────────
121
121
 
122
- /**
123
- * The privilege a tool requires — the same `category`/`privilege` the tool's GraphQL door declares
124
- * (`@privilege(category, privilege, owner?, super?)`), so the fields mirror `PrivilegeObject`.
125
- *
126
- * A tool is not a new door; it is another way into a door that already exists. A tool that creates
127
- * a Board goes through the privilege that creating a Board already needs. Minting an AI-only
128
- * privilege name would put two doors on one effect, and one of them would end up looser.
129
- */
130
- export interface ToolPrivilege {
131
- category: string
132
- privilege: string
133
- /** Domain owners pass without the privilege — mirror the door's `domainOwnerGranted`. */
134
- owner?: boolean
135
- /** Super users pass without the privilege — mirror the door's `superUserGranted`. */
136
- super?: boolean
137
- }
138
-
139
122
  /**
140
123
  * 모든 ToolSpec 공통 메타데이터.
141
124
  */
@@ -151,14 +134,6 @@ export interface ToolSpecBase {
151
134
  themeNote?: string
152
135
  /** JSON Schema for parameters — LLM tool schema 에 그대로 노출. */
153
136
  schema: any
154
- /**
155
- * The privilege a caller needs before this tool is offered or run. See `ToolPrivilege`.
156
- *
157
- * Expected for `write`, `action` and `external` — registration records a
158
- * `tool-privilege-undeclared` warning for one without it (A1 stage one).
159
- */
160
- privilege?: ToolPrivilege
161
-
162
137
  /**
163
138
  * Which GraphQL doors this tool reaches — `['Query.twinNow']`,
164
139
  * `['Query.productionSummary', 'Query.pendingSummary']`.
@@ -173,15 +148,23 @@ export interface ToolSpecBase {
173
148
  * plant's factory overview answers from two, and asking for only one of them would hand back
174
149
  * half of what the caller may not see.
175
150
  *
176
- * Three states, and the boot report keeps them apart:
177
- * - doors and a privilege — the gate checks it
178
- * - `doors: []` — this tool reaches no door at all. The ones that hand back what the surface
179
- * sent (`getOpenScene`, `getStationScreen`) are this: there is nothing behind them to gate.
180
- * - doors with no privilege — the doors themselves declare none yet (twin's queries, waiting
181
- * on AUTH stage two). A tool cannot be stricter than the door it reaches.
151
+ * **This list is the gate** (ADR-0071 decision 2, added to 2026-09-18). A tool used to carry its
152
+ * own copy of what its door asks; the copy went stale the day a door changed its declaration, and
153
+ * the looser of the two won. Now the gate reads the door's own declaration from AUTH's field
154
+ * table (`privilegeOfField`) and asks `checkPermission` with it — the same question, the same
155
+ * answer, one declaration.
156
+ *
157
+ * Three states, and they are not the same thing:
158
+ * - **doors named** — the caller passes each of them, or the tool is neither offered nor run.
159
+ * - **`doors: []`** — this tool reaches no door at all. The ones that hand back what the surface
160
+ * sent (`getOpenScene`) are this: there is nothing behind them to gate.
161
+ * - **no `doors` key** — nothing was said. This is **not** an open tool: it is left out of every
162
+ * conversation and reported by name at boot (`tool-doors-undeclared`). An answer built with it
163
+ * would be hidden from every reader including its author (ADR-0073), so opening it would
164
+ * produce a tool whose answers no one can read.
182
165
  *
183
- * The privilege is **the door's**, copied, and a test in each app compares the copy with the
184
- * directive on that field. When the schema can be read at boot, the copy goes away.
166
+ * A door that declares nothing of its own opens as far as that door opens — a tool is never
167
+ * stricter than the door it reaches, and never softer.
185
168
  */
186
169
  doors?: string[]
187
170
  }