graphlin 0.1.3 → 0.2.1
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +12 -3
- package/docs/decision-service.md +393 -0
- package/docs/extension-authoring.md +553 -0
- package/docs/model-api.md +293 -0
- package/docs/usage.md +472 -0
- package/docs/visualizer-views.md +240 -0
- package/node_modules/@vscode/tree-sitter-wasm/LICENSE +21 -0
- package/node_modules/@vscode/tree-sitter-wasm/README.md +36 -0
- package/node_modules/@vscode/tree-sitter-wasm/SECURITY.md +41 -0
- package/node_modules/@vscode/tree-sitter-wasm/cgmanifest.json +16 -0
- package/node_modules/@vscode/tree-sitter-wasm/package.json +42 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-bash.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-c-sharp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-cpp.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-css.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-go.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ini.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-java.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-javascript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-php.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-powershell.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-python.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-regex.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-ruby.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-rust.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-tsx.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter-typescript.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js +4075 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.wasm +0 -0
- package/node_modules/@vscode/tree-sitter-wasm/wasm/web-tree-sitter.d.ts +1027 -0
- package/package.json +78 -9
- package/plugin.json +4 -2
- package/runtime/architecture/analysis.mjs +344 -0
- package/runtime/architecture/controller.mjs +209 -0
- package/runtime/architecture/evidence.mjs +108 -0
- package/runtime/architecture/profile.mjs +56 -0
- package/runtime/core/evidence.mjs +43 -9
- package/runtime/core/graph.mjs +11 -6
- package/runtime/core/privacy.mjs +1 -0
- package/runtime/daemon/auth.mjs +7 -3
- package/runtime/daemon/diagnostics.mjs +1 -1
- package/runtime/daemon/extension-api.mjs +203 -0
- package/runtime/daemon/lineage.mjs +70 -0
- package/runtime/daemon/manager.mjs +9 -6
- package/runtime/daemon/model-api.mjs +728 -0
- package/runtime/daemon/model-persistence.mjs +220 -0
- package/runtime/daemon/server.mjs +81 -14
- package/runtime/daemon/settings.mjs +11 -3
- package/runtime/decisions/broker.mjs +349 -0
- package/runtime/decisions/contracts.mjs +179 -0
- package/runtime/decisions/evaluation.mjs +305 -0
- package/runtime/decisions/faults.mjs +32 -0
- package/runtime/decisions/index.mjs +818 -0
- package/runtime/decisions/profiles.mjs +93 -0
- package/runtime/decisions/questions.mjs +268 -0
- package/runtime/discovery/index.mjs +2 -0
- package/runtime/discovery/inventory.mjs +160 -0
- package/runtime/discovery/parser.mjs +40 -0
- package/runtime/discovery/structure.mjs +232 -0
- package/runtime/extensions/contracts.mjs +59 -0
- package/runtime/extensions/frame.mjs +64 -0
- package/runtime/extensions/index.mjs +9 -0
- package/runtime/extensions/manifest.mjs +95 -0
- package/runtime/extensions/packages.mjs +222 -0
- package/runtime/extensions/profiles.mjs +36 -0
- package/runtime/extensions/projection.mjs +130 -0
- package/runtime/extensions/registry.mjs +285 -0
- package/runtime/extensions/scene.mjs +105 -0
- package/runtime/extensions/sdk.d.ts +205 -0
- package/runtime/extensions/sdk.mjs +88 -0
- package/runtime/jev/index.mjs +13 -777
- package/runtime/jev/provider.mjs +101 -0
- package/runtime/jev/questions.mjs +16 -258
- package/runtime/jev/wire.mjs +17 -25
- package/runtime/model/changes.mjs +42 -0
- package/runtime/model/history.mjs +124 -0
- package/runtime/model/index.mjs +2 -0
- package/runtime/model/project-model.mjs +1020 -0
- package/runtime/model/records.mjs +240 -0
- package/runtime/pipeline.mjs +267 -55
- package/runtime/platform.mjs +254 -0
- package/runtime/visualizers/blocks.mjs +5 -0
- package/runtime/visualizers/c4.mjs +154 -0
- package/runtime/visualizers/changes.mjs +24 -0
- package/runtime/visualizers/code.mjs +5 -0
- package/runtime/visualizers/index.mjs +23 -0
- package/runtime/visualizers/structure.mjs +120 -0
- package/runtime/visualizers/timeline.mjs +66 -0
- package/runtime/web/app.js +225 -63
- package/runtime/web/extension-frame.js +128 -0
- package/runtime/web/index.html +38 -1
- package/runtime/web/model-client.js +162 -0
- package/runtime/web/platform.js +445 -0
- package/runtime/web/scene.js +111 -0
- package/runtime/web/style.css +51 -0
- package/schemas/graph.schema.json +4 -1
- package/scripts/arguments.mjs +5 -1
- package/scripts/build-packages.mjs +6 -2
- package/scripts/control.mjs +1 -1
- package/scripts/daemon.mjs +2 -1
- package/scripts/extensions.mjs +44 -0
- package/scripts/graphlin.mjs +23 -3
- package/scripts/onboarding.mjs +10 -3
- package/scripts/validate-packages.mjs +54 -8
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Local model API v1
|
|
2
|
+
|
|
3
|
+
`runtime/daemon/model-api.mjs` is a standalone transport module. It does not
|
|
4
|
+
install routes, select a global session, capture source, invoke a classifier,
|
|
5
|
+
persist state, or change a model. The parent daemon owns integration.
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
const api = createModelAPI({
|
|
9
|
+
projectId,
|
|
10
|
+
getSnapshot: options => model.snapshot(options),
|
|
11
|
+
getSessions: () => sessions, // optional; only if snapshots omit sessions
|
|
12
|
+
createCheckpoint: options => model.checkpoint(options), // optional
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// AFTER validating the remote loopback address and the exact Host header:
|
|
16
|
+
if (await api.handle(req, res, { viewerAuthorized: auth.authorized(req) })) return;
|
|
17
|
+
// Continue existing routes and their existing authentication checks.
|
|
18
|
+
|
|
19
|
+
// After accepted model, activity, checkpoint, or disclosure-policy changes:
|
|
20
|
+
api.notify();
|
|
21
|
+
// During daemon shutdown:
|
|
22
|
+
api.close();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`handle` resolves to `false` outside `/api/model/v1/` and otherwise sends the
|
|
26
|
+
response and resolves to `true`. The model callback is synchronous and returns
|
|
27
|
+
schema 2: `{schemaVersion: 2, projectId, revision, sequence, entities, relations,
|
|
28
|
+
interpretations, activity, coverage, sessions, checkpoints}`. It accepts
|
|
29
|
+
`{sessionId?, checkpointId?, scopeId?, persistent: false}`. Checkpoint creation
|
|
30
|
+
is also synchronous and returns a fixed marker with `id`, `projectId`,
|
|
31
|
+
`revision`, `sequence`, optional `label`/`sessionId`, and `at`. Replay calls
|
|
32
|
+
the snapshot callback with that checkpoint ID; it never reconstructs history
|
|
33
|
+
from the current model. Unknown markers should throw
|
|
34
|
+
`MODEL_CHECKPOINT_UNAVAILABLE`; capacity refusal may throw
|
|
35
|
+
`MODEL_CHECKPOINT_CAPACITY`.
|
|
36
|
+
|
|
37
|
+
## Integration authentication requirements
|
|
38
|
+
|
|
39
|
+
Preserve loopback binding, remote-address checks, exactly one allowed Host,
|
|
40
|
+
header/request timeouts, and connection limits in the outer HTTP server.
|
|
41
|
+
Dispatch this module before the legacy blanket query-string rejection and
|
|
42
|
+
viewer-cookie gate. For this prefix only, delegate Origin and bearer checks
|
|
43
|
+
to this module after the address/Host checks. An outer same-origin-only gate
|
|
44
|
+
would prevent explicitly paired browser origins from working.
|
|
45
|
+
|
|
46
|
+
`viewerAuthorized` must mean a successfully authenticated host viewer. Never
|
|
47
|
+
derive it from an external grant. Host POSTs additionally require an exact
|
|
48
|
+
nonopaque Origin matching the validated Host. The outer host must validate
|
|
49
|
+
the scheme and its normal viewer CSRF rules before setting `viewerAuthorized`.
|
|
50
|
+
External bearer tokens are recognized only on this module's GET routes.
|
|
51
|
+
Never make them credentials for legacy endpoints, controls, capture, extension
|
|
52
|
+
installation, or the host evidence inspector. Any bearer header prevents a
|
|
53
|
+
request from acquiring viewer privileges, even alongside a viewer cookie.
|
|
54
|
+
|
|
55
|
+
The snapshot provider must apply **current** local-read/display and path
|
|
56
|
+
exclusion policy on every read, including checkpoints. The transport applies
|
|
57
|
+
a second, fixed field allowlist; it cannot infer project consent. Notify it
|
|
58
|
+
after policy tightening, before further model delivery. Viewer-cookie expiry
|
|
59
|
+
and host shutdown remain outer-server lifecycle responsibilities.
|
|
60
|
+
|
|
61
|
+
## Routes
|
|
62
|
+
|
|
63
|
+
All paths below are relative to `/api/model/v1/`.
|
|
64
|
+
|
|
65
|
+
| Method and path | Result |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| `GET capabilities` | Versions, granted fields, limits and stream bounds |
|
|
68
|
+
| `GET bootstrap` | Initial bounded snapshot plus capabilities |
|
|
69
|
+
| `GET snapshot` | Bounded schema-2 snapshot and continuation cursors |
|
|
70
|
+
| `GET entities`, `relations`, `interpretations`, `activity`, `sessions` | One collection page |
|
|
71
|
+
| `GET entities/:id` | One projected entity |
|
|
72
|
+
| `GET entities/:id/children` | Direct children, paged |
|
|
73
|
+
| `GET history` | Checkpoint markers; `kind=activity` selects observations |
|
|
74
|
+
| `GET events` | SSE scoped snapshot stream |
|
|
75
|
+
| `POST checkpoints` | Viewer-only fixed checkpoint creation |
|
|
76
|
+
| `POST grants` | Viewer-only external-client pairing |
|
|
77
|
+
| `POST grants/revoke` | Viewer-only revocation |
|
|
78
|
+
| `OPTIONS` on GET routes | Narrow preflight for an actively paired Origin |
|
|
79
|
+
|
|
80
|
+
Read selectors are `scope`, `session`, and `checkpoint`. Scope includes the
|
|
81
|
+
named entity, descendants and reachable ancestors; relations and interpretations
|
|
82
|
+
remain inside that selection. Session selects activity/session history, not a
|
|
83
|
+
different project inventory. These selectors are client-local and never invoke
|
|
84
|
+
`selectSession`. Clients retain camera, selected entity, and follow state locally.
|
|
85
|
+
|
|
86
|
+
Snapshot, bootstrap, stream and page routes accept `limit=1..200`. Collection,
|
|
87
|
+
children and history pages accept `cursor`. Capabilities and POST routes accept
|
|
88
|
+
no query parameters. Unknown, empty and duplicate parameters, malformed IDs,
|
|
89
|
+
invalid limits, unexpected JSON fields and duplicate JSON keys are rejected.
|
|
90
|
+
Bodies must be UTF-8 JSON objects of at most 4 KiB; request URLs at most 4 KiB.
|
|
91
|
+
Tokens in query strings are never accepted. Checkpoint POST body:
|
|
92
|
+
`{"label":"Task baseline","sessionId":"session-example"}`; both keys are optional.
|
|
93
|
+
|
|
94
|
+
## Pages, coverage and revision consistency
|
|
95
|
+
|
|
96
|
+
Each JSON response and SSE frame is bounded by 512 KiB. Snapshots contain at
|
|
97
|
+
most 200 records **across all six collections**, reserving room for activity
|
|
98
|
+
even with a large entity inventory. Byte limits can reduce this further.
|
|
99
|
+
`partial` and `pages.<collection>.{total,returned,nextCursor}` expose omitted
|
|
100
|
+
pages. A snapshot cursor goes to the corresponding collection route;
|
|
101
|
+
checkpoint cursors go to `history`. Keep exactly the same selectors.
|
|
102
|
+
|
|
103
|
+
Pages return `{items, page: {total, offset, returned, complete, nextCursor}}`
|
|
104
|
+
along with model revision, model sequence, selection and transport metadata.
|
|
105
|
+
Entities use deterministic breadth-first containment order: roots and ancestors
|
|
106
|
+
precede children, with siblings sorted by ID. The initial bounded snapshot
|
|
107
|
+
therefore preserves retained ancestor groups. Subsequent pages extend that
|
|
108
|
+
same assembly; they do not repeat its ancestors. Other collections use stable
|
|
109
|
+
ID ordering; activity and checkpoint ordering uses model sequence and then ID.
|
|
110
|
+
Cursors are signed and bind project/daemon epoch,
|
|
111
|
+
principal, collection or parent, selectors, revision, model sequence, and
|
|
112
|
+
the complete safe projection fingerprint. They expire after five minutes.
|
|
113
|
+
Changes, policy redaction, or expiry produce `409 stale_cursor`: discard the
|
|
114
|
+
partial assembly and obtain a fresh snapshot. Never merge inconsistent pages.
|
|
115
|
+
A cursor for a different principal, scope, or collection is invalid.
|
|
116
|
+
|
|
117
|
+
A 20,000-entity / 40,000-relation model is paged rather than rejected for
|
|
118
|
+
snapshot size. Inventory coverage exports allowlisted counts, completeness,
|
|
119
|
+
deferred counts, relationship counts, detail counts and parsing status.
|
|
120
|
+
`coverage.parsing` preserves `queued`, `active`, `deferred`, `parsed`, `failed`,
|
|
121
|
+
`stale`, `omitted`, and a bounded error code. It never contains parser source.
|
|
122
|
+
|
|
123
|
+
`coverage.lineage` preserves a bounded `id`, exactly one of the statuses `git`,
|
|
124
|
+
`not_git`, or `unavailable`, and optional safe `branch` and 40- or 64-character
|
|
125
|
+
hexadecimal `head`. Invalid required fields omit the lineage record; invalid
|
|
126
|
+
optional fields and unknown properties are stripped. Checkpoint GET and SSE
|
|
127
|
+
reads preserve the lineage recorded in that checkpoint, subject to current
|
|
128
|
+
disclosure policy. The API does not overlay the live branch or infer freshness;
|
|
129
|
+
the model owns branch/HEAD reconciliation. Lineage requires the `coverage`
|
|
130
|
+
grant field and participates in page consistency checks.
|
|
131
|
+
|
|
132
|
+
Entity `createdAtSequence` preserves a positive integer creation-observation
|
|
133
|
+
marker. `coverage.enumerations` preserves strictly validated certificates:
|
|
134
|
+
artifact/scope IDs, hash/generation, extractor and version, identity version,
|
|
135
|
+
covered line ranges, omission codes, capability and completeness. No arbitrary
|
|
136
|
+
certificate properties or range payloads are copied. A certificate cannot be
|
|
137
|
+
complete with invalid/missing ranges, unknown versions, omissions or a
|
|
138
|
+
nonparsed capability. Duplicate artifact certificates are withheld.
|
|
139
|
+
|
|
140
|
+
Certificates are bounded to 64 records/64 KiB and count toward the snapshot's
|
|
141
|
+
200-record budget. At most half of a caller's smaller `limit` is allocated to
|
|
142
|
+
certificates. Scoped reads retain certificates for artifacts supporting the
|
|
143
|
+
selected entities. `coverage.enumerationCoverage` reports `total`, `returned`,
|
|
144
|
+
`omitted` and `truncated`; omitted proof must remain unknown in comparisons.
|
|
145
|
+
Clients can narrow the scope to recover relevant proof in a large project.
|
|
146
|
+
Inventory file/artifact arrays are still represented by counts.
|
|
147
|
+
`coverage.projection.omitted` counts records
|
|
148
|
+
withheld by record validation. Unknown fields and nested extension payloads are
|
|
149
|
+
never copied. A transport page being complete does not establish complete
|
|
150
|
+
discovery, source support, or runtime execution.
|
|
151
|
+
|
|
152
|
+
## Stream and reconnect
|
|
153
|
+
|
|
154
|
+
Use streaming `fetch` with an Authorization header for external clients.
|
|
155
|
+
Native `EventSource` does not supply an arbitrary bearer header.
|
|
156
|
+
|
|
157
|
+
The SSE `snapshot` event contains the same bounded scoped schema as a snapshot
|
|
158
|
+
GET. Its ID is `epoch:transportSequence:selectionKey`; model `sequence` and
|
|
159
|
+
`revision` remain separate. Each `notify()` advances the transport sequence,
|
|
160
|
+
including activity-only and disclosure changes. Delivery is scheduled outside
|
|
161
|
+
the notifying call and can coalesce updates. The initial subscriber is
|
|
162
|
+
registered before its synchronous snapshot is obtained, without an intervening
|
|
163
|
+
await, so updates cannot fall between snapshot and subscription.
|
|
164
|
+
|
|
165
|
+
Reconnect with `Last-Event-ID`. The last 128 transport positions are retained.
|
|
166
|
+
A matching epoch, principal/selection and retained position yields one **fresh,
|
|
167
|
+
full replacement** snapshot with `transport.resume.fromSequence` and
|
|
168
|
+
`coalesced: true`. This is snapshot continuity, not replay of every intermediate
|
|
169
|
+
transition. The retained ring stores only positions, never old payloads.
|
|
170
|
+
Activity history comes from the model's ordered observations and named
|
|
171
|
+
checkpoints. Clients needing every observation must inspect model coverage and
|
|
172
|
+
history bounds; transport continuity does not imply unlimited model history.
|
|
173
|
+
|
|
174
|
+
Wrong lineage/selection, a future/evicted position or missing history permission
|
|
175
|
+
yields a `reset` event containing a reason and retained bounds, then a fresh
|
|
176
|
+
snapshot. Drop the old partial page assembly and scene. Duplicate snapshot
|
|
177
|
+
positions can be ignored within the same live selection. No semantic deltas
|
|
178
|
+
are currently emitted. Checkpoint streams continue to read their fixed marker.
|
|
179
|
+
Revocation/policy changes cannot recover data already copied by a client.
|
|
180
|
+
|
|
181
|
+
There are at most 16 open streams. A stream whose queued bytes plus its next
|
|
182
|
+
frame would exceed 512 KiB is disconnected; it can recover by reconnecting.
|
|
183
|
+
Snapshot failures close affected streams with `unavailable`. `notify()` and
|
|
184
|
+
`close()` are safe after shutdown. The provider remains responsible for keeping
|
|
185
|
+
its synchronous snapshot work bounded.
|
|
186
|
+
|
|
187
|
+
## Pairing an external client
|
|
188
|
+
|
|
189
|
+
An authenticated same-origin host POST can create a grant:
|
|
190
|
+
|
|
191
|
+
```json
|
|
192
|
+
{
|
|
193
|
+
"projectId": "project-example",
|
|
194
|
+
"fields": ["entities", "relations", "activity", "coverage"],
|
|
195
|
+
"history": false,
|
|
196
|
+
"ttlSeconds": 900,
|
|
197
|
+
"origins": []
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The response contains public grant metadata and a one-time plaintext `token`.
|
|
202
|
+
Only its SHA-256 hash is retained for authentication. A token is short-lived,
|
|
203
|
+
project-bound and read-only. `fields` is an explicit nonempty subset of
|
|
204
|
+
`entities`, `relations`, `interpretations`, `activity`, `coverage`, `sessions`,
|
|
205
|
+
and `checkpoints`. TTL is 1–3600 seconds (default 900); at most 32 grants exist.
|
|
206
|
+
Grants are memory-only and all expire on daemon restart.
|
|
207
|
+
|
|
208
|
+
`history: false` allows current snapshots, including the current activity
|
|
209
|
+
window; it forbids checkpoint/session selectors, history routes and historical
|
|
210
|
+
stream resume, and withholds checkpoint markers. Requesting an ungranted
|
|
211
|
+
collection is forbidden. Empty arrays stand in for ungranted snapshot fields;
|
|
212
|
+
coverage contains only transport withholding counts unless granted.
|
|
213
|
+
|
|
214
|
+
Native clients omit Origin. Browser clients must use an exact HTTP(S) origin
|
|
215
|
+
listed in the grant, such as `https://visualizer.example`. Wildcards, URL paths,
|
|
216
|
+
credentials and opaque `null` origins are rejected. CORS responses echo only
|
|
217
|
+
that origin, allow GET and Authorization/Last-Event-ID headers, and never allow
|
|
218
|
+
credentials. A preflight reveals no model and requires an active paired origin.
|
|
219
|
+
The subsequent GET must still authenticate its individual token and Origin.
|
|
220
|
+
|
|
221
|
+
Revoke with `POST grants/revoke`, body `{"grantId":"grant-example"}`.
|
|
222
|
+
Revocation ends existing streams immediately. Independent expiry timers plus
|
|
223
|
+
an idle sweep end expired streams without requiring a model update or another
|
|
224
|
+
request. Neither external bearer credentials nor a paired origin can create
|
|
225
|
+
checkpoints/grants, revoke other clients, install extensions or control capture.
|
|
226
|
+
|
|
227
|
+
Every route, history response and stream uses the same record allowlists.
|
|
228
|
+
There is no raw source, excerpt, prompt, transcript, hook input, credential,
|
|
229
|
+
absolute locator, or arbitrary namespace payload field in this contract.
|
|
230
|
+
Labels are approved display metadata supplied by the core, additionally checked
|
|
231
|
+
by the local secret/text filter. This is not a general-purpose sanitizer for
|
|
232
|
+
arbitrary source embedded in labels; providers must never supply such content.
|
|
233
|
+
|
|
234
|
+
## Separate model persistence
|
|
235
|
+
|
|
236
|
+
`runtime/daemon/model-persistence.mjs` is an optional, separate parent integration.
|
|
237
|
+
It neither opens nor changes legacy `state.json`. Use the canonical private
|
|
238
|
+
project-data directory already established by the daemon:
|
|
239
|
+
|
|
240
|
+
```js
|
|
241
|
+
const modelStore = createModelPersistence(
|
|
242
|
+
path.join(paths.directory, 'model-state.json'),
|
|
243
|
+
);
|
|
244
|
+
const restoredModel = await modelStore.load(); // undefined when absent/unusable
|
|
245
|
+
// Pass restoredModel to the model constructor; it revalidates evidence/policy.
|
|
246
|
+
modelStore.schedule(model.snapshot({ persistent: true }));
|
|
247
|
+
await modelStore.flush(); // explicit durability point
|
|
248
|
+
await modelStore.close(); // stop scheduling and drain before releasing daemon lock
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The interface is `createModelPersistence(filename, {projectId?, maxBytes?,
|
|
252
|
+
debounceMs?, now?} = {})`. The filename-only call matches the legacy factory.
|
|
253
|
+
An omitted project ID binds to the first successfully loaded or scheduled
|
|
254
|
+
snapshot; rejected data never binds it. Later cross-project data is refused.
|
|
255
|
+
Passing `{projectId: paths.projectId}` additionally validates the project on
|
|
256
|
+
the first load. Full SHA-256 project IDs are supported.
|
|
257
|
+
Its methods are `load`, `schedule`, `flush`, `close` and
|
|
258
|
+
`stats`. `schedule` synchronously captures immutable JSON and returns whether
|
|
259
|
+
the snapshot was accepted. Calls coalesce within a 100 ms window; a running
|
|
260
|
+
write retains at most one replacement snapshot, so continuous updates do not
|
|
261
|
+
starve persistence. `flush` and `close` wait for accepted writes, including a
|
|
262
|
+
replacement queued during a write. The parent supplies its existing exclusive
|
|
263
|
+
daemon lock; this module is not a multiprocess lock service.
|
|
264
|
+
|
|
265
|
+
The disk envelope is `{schemaVersion: 2, savedAt, projectId, snapshot}`.
|
|
266
|
+
The **complete envelope** must fit 48 MiB; `maxBytes` can lower but never raise
|
|
267
|
+
that ceiling. Byte counting precedes full JSON encoding, including UTF-8 and
|
|
268
|
+
escape expansion. Oversized or invalid input is refused intact: no records,
|
|
269
|
+
checkpoint states or support are silently trimmed. The previous accepted file
|
|
270
|
+
and pending snapshot survive refusal. Persistent `.storage` and checkpoint
|
|
271
|
+
states are preserved; they still cannot pass through HTTP API projection.
|
|
272
|
+
|
|
273
|
+
The parent must supply `model.snapshot({persistent: true})` under current
|
|
274
|
+
policy, rather than a display snapshot or raw model internals. Persistence does
|
|
275
|
+
not grant new source access, reinterpret evidence, or replace policy filtering.
|
|
276
|
+
Undefined object fields used for withheld paths are omitted using normal JSON
|
|
277
|
+
semantics; unsupported values, accessors, cycles and oversized nesting are refused.
|
|
278
|
+
Loading likewise does not establish current evidence validity; restore through
|
|
279
|
+
the core model and reapply current policy before serving anything.
|
|
280
|
+
|
|
281
|
+
Writes use an exclusive 0600 temporary file in the same private directory,
|
|
282
|
+
file sync, atomic rename and directory sync where supported. The target and
|
|
283
|
+
directory must be owned by the current user, private, and free of symlink or
|
|
284
|
+
hard-link substitutions. The directory must already exist at its canonical
|
|
285
|
+
path. The legacy `state.json` filename is explicitly rejected. Failed writes
|
|
286
|
+
clean their temporary file and report aggregate `persistenceFailures` through
|
|
287
|
+
`stats`; no contents, credentials or paths are logged.
|
|
288
|
+
|
|
289
|
+
Missing, malformed, oversized, incompatible, wrong-project or unsafe model
|
|
290
|
+
files return `undefined`. They are never automatically removed, and valid
|
|
291
|
+
model files have no implicit age expiry. The parent can retain legacy-only
|
|
292
|
+
operation when model load fails. Rollback continues to read untouched
|
|
293
|
+
`state.json`; the parent owns any deliberate migration and package rollback.
|