clearotron 0.2.2 → 0.2.3
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/.env.example +28 -0
- package/.nvmrc +1 -1
- package/INSTALL.md +13 -11
- package/README.md +11 -16
- package/THIRD-PARTY-NOTICES.md +1 -1
- package/bin/clearotron.mjs +22 -0
- package/bin/example.mjs +4 -0
- package/bin/onboard.mjs +195 -19
- package/bin/start.mjs +154 -8
- package/build-info.json +2 -2
- package/demo/full-country-search/run/_driver/framework.json +1 -1
- package/demo/global-preliminary-search/run/_driver/framework.json +1 -1
- package/demo/knockout-search/run/_driver/framework.json +1 -1
- package/demo/multi-country-focus-search/run/_driver/framework.json +1 -1
- package/docs/E2E.md +5 -5
- package/docs/PORTAL.md +2 -2
- package/docs/README.md +5 -4
- package/docs/SECURITY.md +17 -0
- package/docs/architecture/04-configuration-reference.md +2 -0
- package/docs/architecture/05-config-governance.md +14 -0
- package/docs/architecture/05-customer-profiles.md +2 -2
- package/docs/configuration.md +4 -3
- package/driver/CHANGELOG.md +56 -0
- package/driver/common-law-receipts.mjs +1 -1
- package/driver/config-staleness.mjs +1 -1
- package/driver/dev-portal.mjs +2 -2
- package/driver/driver.config.mjs +1 -1
- package/driver/engine/cli-version.mjs +103 -0
- package/driver/enqueue-schema.mjs +8 -8
- package/driver/feedback-store.mjs +1 -1
- package/driver/findings-model.mjs +52 -2
- package/driver/flag-snapshot.mjs +20 -0
- package/driver/framework.mjs +1 -1
- package/driver/gateway.mjs +18 -1
- package/driver/package.json +3 -3
- package/driver/party-facts.mjs +2 -2
- package/driver/phase0.mjs +1 -1
- package/driver/pipeline.mjs +5 -5
- package/driver/plain-register.mjs +120 -0
- package/driver/portal-access.mjs +1 -1
- package/driver/portal-config-view.mjs +40 -1
- package/driver/portal-families.mjs +1 -1
- package/driver/portal-report.mjs +1 -1
- package/driver/portal-service.mjs +26 -7
- package/driver/portal-upstream.mjs +2 -2
- package/driver/predelivery-lint.mjs +121 -1
- package/driver/profile-page.html +10 -2
- package/driver/profile-service.mjs +4 -4
- package/driver/profiles/README.md +3 -3
- package/driver/profiles.mjs +144 -12
- package/driver/publish/index.mjs +2 -2
- package/driver/publish/knockout.mjs +7 -1
- package/driver/publish/render-knockout.mjs +453 -118
- package/driver/publish/render.mjs +2 -2
- package/driver/reference-strip-signatures.mjs +20 -0
- package/driver/search-policy.mjs +2 -2
- package/driver/skills/blind-frame/SKILL.md +1 -1
- package/driver/skills/knockout-assess/SKILL.md +84 -0
- package/driver/skills/matter-frame/SKILL.md +6 -6
- package/driver/skills/matter-frame/watchlist-reference.md +2 -2
- package/driver/skills/prelim-common-law/SKILL.md +1 -1
- package/driver/skills/prelim-search/SKILL.md +1 -1
- package/driver/skills/prelim-search/report-prose.md +82 -0
- package/driver/skills/prelim-search/risk-framework-demo.manifest.json +1 -1
- package/driver/skills/prelim-search/risk-framework-triage.manifest.json +1 -1
- package/driver/skills/prelim-search/risk-framework.manifest.json +1 -1
- package/driver/skills/prelim-search/synthesis-rules.md +1 -1
- package/driver/skills/prelim-variants/SKILL.md +3 -3
- package/driver/stages.mjs +4 -4
- package/driver/suite-census.json +134 -38
- package/driver/unit-inventory.mjs +184 -27
- package/driver/verify.mjs +1 -1
- package/driver/whatif-memo-run.mjs +26 -4
- package/driver/whatif-memo.mjs +19 -1
- package/examples/README.md +2 -2
- package/examples/grants.example.json +6 -7
- package/mcp-server/CHANGELOG.md +2 -0
- package/mcp-server/lib/scrub.mjs +1 -1
- package/mcp-server/lib/whatif.mjs +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/ops/EXAMPLES.md +2 -2
- package/mcp-server/server.mjs +1 -1
- package/package.json +28 -10
- package/portal-ui/dist/assets/{index-Ct3iQtZ2.css → index-1ziUJX1E.css} +43 -2
- package/portal-ui/dist/assets/{index-BUGgiYtF.js → index-CEYvXdqH.js} +226 -49
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/term-shape.mjs +1 -1
- package/providers/oauth-mcp-bridge/CHANGELOG.md +2 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/uspto-local/src/index-store.js +2 -2
- package/scripts/citation-line-check.mjs +121 -2
- package/scripts/env-classify.mjs +67 -3
- package/scripts/live-surface-check.mjs +38 -1
- package/scripts/nightly-notice.mjs +0 -2
- package/scripts/preinstall-node-check.mjs +24 -0
- package/scripts/strip-tracker-citations.mjs +29 -6
- package/scripts/test-run.mjs +16 -0
- package/shared/anon-overlay.mjs +3 -3
- package/shared/customer-store.mjs +1 -1
- package/shared/invocation.mjs +28 -0
- package/shared/names-in-force.mjs +2 -0
- package/shared/node-floor.mjs +97 -0
- package/shared/staff-domain.mjs +154 -0
- package/shared/store-in-repo.mjs +23 -2
- package/skills/clearotron-ops/SKILL.md +1 -1
- package/driver/recipes/README.md +0 -49
- package/driver/skills/prelim-search/risk-framework-aurora.manifest.json +0 -19
- package/driver/skills/prelim-search/risk-framework-aurora.md +0 -77
- package/driver/skills/prelim-search/risk-framework-zephyr.manifest.json +0 -14
- package/driver/skills/prelim-search/risk-framework-zephyr.md +0 -33
- package/driver/skills/prelim-search/worked-examples-aurora.md +0 -10
- package/driver/skills/prelim-search/worked-examples-zephyr.md +0 -10
- package/scripts/ai-page-render-check.mjs +0 -532
- package/scripts/clearances-render-check.mjs +0 -480
- package/scripts/composer-render-check.mjs +0 -1027
- package/scripts/home-render-check.mjs +0 -526
- package/scripts/portal-lifecycle-check.mjs +0 -639
|
@@ -123,16 +123,22 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
123
123
|
// exists to make impossible — and a mechanism that installs itself is the easiest kind to ship
|
|
124
124
|
// undeclared.
|
|
125
125
|
unit: "clearotron-deploy", runsOn: ["test"],
|
|
126
|
-
measured: "2026-09-
|
|
126
|
+
measured: "2026-09-08, the test deployment: the timer and its service are installed for that deployment's own user, the timer is enabled and ACTIVE, and it last fired within the hour — `systemctl --user list-timers`. The service's own state says nothing about this: it is `inactive` between runs and `inactive` when the timer has been stopped. The timer's ActiveState is what says anything will start it again.",
|
|
127
127
|
tracked: ["clearotron-deploy.service", "clearotron-deploy.timer"],
|
|
128
|
-
note: "the install's own updater.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
note: "the install's own updater. Placed on the test deployment, where it pulls hourly; production takes it when the owner asks.",
|
|
129
|
+
// IT WAS AN ORPHAN, AND THE CONDITION IT NAMED HAS BEEN MET. This entry carried an `orphanReason`
|
|
130
|
+
// saying it had been shipped and installed nowhere, and that `runsOn` would gain "test" the day a
|
|
131
|
+
// deploy placed it. It has: the timer is installed, enabled and firing, and `runsOn` says so. The prose
|
|
132
|
+
// stayed behind the field, which is the ordinary way a declaration and its explanation come apart —
|
|
133
|
+
// and it matters more here than usual, because this entry is now what puts a real timer into the
|
|
134
|
+
// timer list. An entry describing itself as installed nowhere, whose timer the check asks systemd
|
|
135
|
+
// about by name, is a contradiction a reader has to resolve before trusting either half.
|
|
136
|
+
//
|
|
137
|
+
// AND THE POSTURE IS NOW STATED, because it is a change in what a red means rather than in code: a
|
|
138
|
+
// STOPPED deploy timer FAILS. The service reads `inactive` between runs and `inactive` when nothing
|
|
139
|
+
// will ever run it again, so the timer's own state is the only thing separating a deployment that
|
|
140
|
+
// tracks the main branch from one quietly frozen on whatever it last built. If the timer is ever
|
|
141
|
+
// held deliberately, a check that says so for as long as the hold lasts is the behaviour to want.
|
|
136
142
|
},
|
|
137
143
|
{
|
|
138
144
|
// ── `tracked` WAS A CLAIM ABOUT A FILE THAT HAS NEVER EXISTED (tracker issue 175) ──────────────
|
|
@@ -142,10 +148,23 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
142
148
|
// `live-surface-check` FAILED on a healthy box — the worst kind of red, because it teaches a reader
|
|
143
149
|
// to scroll past the check that would catch a unit genuinely gone missing.
|
|
144
150
|
//
|
|
145
|
-
// THE ROW STAYS, AND DELETING IT WOULD HAVE BEEN THE WRONG FIX.
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
151
|
+
// THE ROW STAYS, AND DELETING IT WOULD HAVE BEEN THE WRONG FIX. What was false then was the
|
|
152
|
+
// `tracked` claim, not the row. Removing the entry would make this inventory stop knowing about a
|
|
153
|
+
// unit it ships nothing for — the same failure it exists to prevent, pointed the other way.
|
|
154
|
+
//
|
|
155
|
+
// ── AND THE OTHER HALF OF THAT PARAGRAPH HAS NOW BEEN MEASURED FALSE TOO ───────────────────────
|
|
156
|
+
//
|
|
157
|
+
// It read "The unit is LIVE ON PRODUCTION, as the note below has said all along", and it had been
|
|
158
|
+
// said all along without ever being enumerated. Production was read on 2026-09-08 — every account
|
|
159
|
+
// on the machine, unit file names and enable state — and no unit of this name exists under any of
|
|
160
|
+
// them. An earlier reading the same day reached the same answer from the other direction while
|
|
161
|
+
// closing a different question.
|
|
162
|
+
//
|
|
163
|
+
// So `runsOn` is empty and the note says what was measured. This is a change to what this
|
|
164
|
+
// repository CLAIMS about production, made because a reading contradicted the claim, and not a
|
|
165
|
+
// statement that production should not run it — that is a deployment question and it is the
|
|
166
|
+
// owner's. If the answer is yes, `runsOn` gains "prod" in the change that deploys it, against a
|
|
167
|
+
// fresh enumeration, which is this file's own rule rather than an exception to it.
|
|
149
168
|
//
|
|
150
169
|
// Declared the way its siblings are: `tracked: null` with the reason, which this file's own header
|
|
151
170
|
// calls the sanctioned way to say the repo does not carry one.
|
|
@@ -156,16 +175,31 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
156
175
|
// is a change which ADDS a file, measured against the deployed copy, and it is not this one. Two
|
|
157
176
|
// prose references in driver/systemd/render-units.mjs also describe this file as though it were in
|
|
158
177
|
// the tree; they are stale today either way.
|
|
159
|
-
unit: "profile-service", runsOn: [
|
|
178
|
+
unit: "profile-service", runsOn: [],
|
|
179
|
+
measured: "2026-09-08: production read by account — the deployment account carries six clearotron-* "
|
|
180
|
+
+ "unit files and the doctrine-sync timer, the legacy account carries five units belonging to the "
|
|
181
|
+
+ "other product plus unloadable residue, and no unit of THIS name is under either. Names and enable "
|
|
182
|
+
+ "state only; no unit contents were read.",
|
|
183
|
+
tracked: null,
|
|
160
184
|
untrackedReason: "the repository has never carried this file — `git log --all` on the path is empty. "
|
|
161
|
-
+ "It
|
|
162
|
-
+ "
|
|
185
|
+
+ "It was described as deploying from a copy held on the deployment itself, in the same "
|
|
186
|
+
+ "identity-provider template family as two other pre-rename names; the 2026-09-08 reading finds no "
|
|
187
|
+
+ "such copy under any account, so that description is history rather than current state. Whether a "
|
|
188
|
+
+ "placeholder should now ship is open: see above.",
|
|
163
189
|
// NO LONGER RESOLVED AT INSTALL. It carried `@CLEAROTRON_CHECKOUT_DIR@` because
|
|
164
190
|
// it loaded no EnvironmentFile and so had no `${VAR}` systemd could expand. The owner's one-config-
|
|
165
191
|
// per-server-box ruling gives it `EnvironmentFile=%h/.env` like every other service, which makes the
|
|
166
192
|
// checkout path an ordinary systemd expansion and leaves no placeholder to render.
|
|
167
|
-
note: "
|
|
168
|
-
+ "
|
|
193
|
+
note: "NOT RUNNING ANYWHERE, measured 2026-09-08. This row said LIVE ON PRODUCTION for as long as it "
|
|
194
|
+
+ "existed and no enumeration ever supported it. Was a TEMPLATE unit carrying identity-provider "
|
|
195
|
+
+ "values inline; generic since tracker issue 1925.",
|
|
196
|
+
orphanReason: "CLAIMED A DEPLOYMENT IT WAS NEVER MEASURED ON, which is a FOURTH kind of orphan and "
|
|
197
|
+
+ "the only one that was ever a wrong claim rather than a waiting decision: the other three run "
|
|
198
|
+
+ "under another name, were deliberately switched off, or have simply never been installed. This "
|
|
199
|
+
+ "one was declared live and read as absent. The row is kept because the deployment question is "
|
|
200
|
+
+ "open and belongs to the owner — whether production should run this service at all — and an "
|
|
201
|
+
+ "inventory that deleted the row would lose the only place that question is written down. It "
|
|
202
|
+
+ "gains a deployment the day an enumeration shows it, never the day someone intends it.",
|
|
169
203
|
},
|
|
170
204
|
{
|
|
171
205
|
// RUNS ON NO BOX YET, AND THAT IS THE HONEST DECLARATION. `runsOn: ["prod"]` is a claim about a
|
|
@@ -249,8 +283,11 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
249
283
|
+ "alternatives, and running both puts a second claimant on one queue.",
|
|
250
284
|
},
|
|
251
285
|
{
|
|
252
|
-
unit: "trademark-portal", runsOn: [
|
|
253
|
-
|
|
286
|
+
unit: "trademark-portal", runsOn: [],
|
|
287
|
+
orphanReason: "THE PRE-RENAME PORTAL. The deployment account runs `clearotron-portal`; this name is "
|
|
288
|
+
+ "what that service used to be called, and the 2026-09-08 reading finds no unit of this name under "
|
|
289
|
+
+ "any account. It ran on production until the rename and the entry was never re-measured after it.",
|
|
290
|
+
measured: "2026-09-08: production read by account — the deployment account carries six clearotron-* unit files and the doctrine-sync timer, the legacy account carries five units of the other product plus unloadable residue, and no unit of THIS name is under either. Names and enable state only. This line previously carried a 2026-09-07 reading of the test deployment — LoadState=not-found there, with the production claim left alone because production had not been measured. It has been now, and this is that reading.", tracked: null,
|
|
254
291
|
untrackedReason: "the deployed copy carries real Cloudflare Access team/AUD/domain values inline. "
|
|
255
292
|
+ "A tracked file would be the placeholder TEMPLATE, merged by hand after a "
|
|
256
293
|
+ "diff — writing one carelessly replaces working auth with placeholders that look configured.",
|
|
@@ -261,13 +298,19 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
261
298
|
supersededName: "portal-service.service (deleted, owner-ruled 2026-08-14)",
|
|
262
299
|
},
|
|
263
300
|
{
|
|
264
|
-
unit: "trademark-ops-mcp", runsOn: [
|
|
265
|
-
|
|
301
|
+
unit: "trademark-ops-mcp", runsOn: [],
|
|
302
|
+
orphanReason: "THE PRE-RENAME OPERATOR DOOR. The deployment account runs `clearotron-mcp-face`; the "
|
|
303
|
+
+ "2026-09-08 reading finds no unit of this name under any account. Same rename as the portal it "
|
|
304
|
+
+ "serves, and the same claim carried across it unmeasured.",
|
|
305
|
+
measured: "2026-09-08: production read by account — the deployment account carries six clearotron-* unit files and the doctrine-sync timer, the legacy account carries five units of the other product plus unloadable residue, and no unit of THIS name is under either. Names and enable state only. This line previously carried a 2026-09-07 reading of the test deployment — LoadState=not-found there, with the production claim left alone because production had not been measured. It has been now, and this is that reading.", tracked: null,
|
|
266
306
|
untrackedReason: "same CF Access inline-values shape as the portal it serves.",
|
|
267
307
|
},
|
|
268
308
|
{
|
|
269
|
-
unit: "client-mcp", runsOn: [
|
|
270
|
-
|
|
309
|
+
unit: "client-mcp", runsOn: [],
|
|
310
|
+
orphanReason: "THE PRE-RENAME CLIENT DOOR. `clearotron-client-mcp` runs on both the production and "
|
|
311
|
+
+ "the test deployments; the 2026-09-08 reading finds no unit of THIS name under any account. The "
|
|
312
|
+
+ "tracked template stays because the repository still ships it — see the note.",
|
|
313
|
+
measured: "2026-09-08: production read by account — the deployment account carries six clearotron-* unit files and the doctrine-sync timer, the legacy account carries five units of the other product plus unloadable residue, and no unit of THIS name is under either. Names and enable state only. This line previously carried a 2026-09-07 reading of the test deployment — LoadState=not-found there, with the production claim left alone because production had not been measured. It has been now, and this is that reading.", tracked: ["client-mcp.service"],
|
|
271
314
|
note: "TRACKED, in mcp-server/remote/ — this entry said it had no file, and the file was there. It is "
|
|
272
315
|
+ "a banner-marked TEMPLATE, so the live copy differing from it is the arrangement, not drift. "
|
|
273
316
|
+ "#1147 ADDED THE TEST BOX (2026-08-18): the client surface ran nowhere but production, so the test "
|
|
@@ -278,18 +321,79 @@ export const UNIT_INVENTORY = Object.freeze([
|
|
|
278
321
|
+ "not the default.",
|
|
279
322
|
},
|
|
280
323
|
{
|
|
281
|
-
unit: "client-mcp-apikey", runsOn: ["
|
|
324
|
+
unit: "client-mcp-apikey", runsOn: ["test"],
|
|
325
|
+
measured: "2026-09-08, read by account: enabled and present on the TEST deployment, beside the "
|
|
326
|
+
+ "renamed `clearotron-client-mcp`. Absent from production under any account. The entry declared "
|
|
327
|
+
+ "production and the reading puts it on the other deployment — the claim was not stale, it named "
|
|
328
|
+
+ "the wrong machine. Why it runs there at all beside the renamed door is a separate question.",
|
|
329
|
+
tracked: ["client-mcp-apikey.service"],
|
|
282
330
|
note: "TRACKED, in mcp-server/remote/. Same correction as client-mcp: the old reason claimed the live "
|
|
283
331
|
+ "key made a tracked file impossible, and the tracked TEMPLATE — which holds no key — already existed.",
|
|
284
332
|
},
|
|
285
333
|
{
|
|
286
|
-
unit: "trademark-artifacts-http", runsOn: [
|
|
334
|
+
unit: "trademark-artifacts-http", runsOn: [],
|
|
335
|
+
orphanReason: "A PRE-RENAME NAME OF THE RETIRED DEPLOYMENT, and the one of them whose tracked file "
|
|
336
|
+
+ "is GENERIC rather than a template — so the file is ready to be compared against a live copy the "
|
|
337
|
+
+ "day something runs it, and nothing does. Read as absent under every account on 2026-09-08.",
|
|
338
|
+
measured: "2026-09-08: production read by account — no unit of this name is under the deployment account or the legacy one. Names and enable state only; no unit contents were read.", tracked: ["trademark-artifacts-http.service"],
|
|
287
339
|
note: "TRACKED, in mcp-server/remote/. GENERIC, not a template: it carries no banner and no "
|
|
288
340
|
+ "placeholder, and defers its CF Access values to the EnvironmentFile — so its live copy is "
|
|
289
341
|
+ "expected to MATCH the tracked file, and a difference is real drift.",
|
|
290
342
|
},
|
|
291
343
|
{
|
|
292
|
-
unit: "
|
|
344
|
+
unit: "clearotron-mcp-local", runsOn: ["prod"], tracked: null,
|
|
345
|
+
untrackedReason: "the fifth hand-made door on production, on 18793, kept as it is by ruling while "
|
|
346
|
+
+ "the product fix lands. Known and hand-made is not the same as declared: it was running on "
|
|
347
|
+
+ "production and named by no inventory, no unit file and no line of code in this repo, so the "
|
|
348
|
+
+ "health check could not see it and nothing would have said if it stopped. Declared here to end "
|
|
349
|
+
+ "that, not to bless the arrangement.",
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
unit: "clearotron-doctrine-sync", runsOn: ["prod"], tracked: null,
|
|
353
|
+
// WHAT SYSTEMD KNOWS THIS UNIT AS, which `tracked` cannot say here. For a tracked unit the file
|
|
354
|
+
// list carries the suffixes — `clearotron-deploy` names its `.service` AND its `.timer` — so a
|
|
355
|
+
// reader deriving systemd queries from the entry gets both. An UNTRACKED unit ships no files, so
|
|
356
|
+
// `tracked: null` leaves nothing to derive from and the bare name silently means `.service`.
|
|
357
|
+
// Stated separately so the two questions stay apart: `tracked` is what this repo SHIPS,
|
|
358
|
+
// `systemdUnits` is what the box RUNS.
|
|
359
|
+
systemdUnits: ["clearotron-doctrine-sync.service", "clearotron-doctrine-sync.timer"],
|
|
360
|
+
// ── THE ONE WHOSE SILENCE REACHES A CLIENT ────────────────────────────────────────────────────
|
|
361
|
+
//
|
|
362
|
+
// This is the timer half of production's doctrine refresh. The stages read doctrine to decide what
|
|
363
|
+
// they do, so if it stops, production runs clearances against whatever doctrine last landed and NO
|
|
364
|
+
// SURFACE ANYWHERE SAYS SO — the client receives a report that looks exactly like a current one.
|
|
365
|
+
//
|
|
366
|
+
// That is a client-facing failure with no detector, which is what the drift check exists for, and
|
|
367
|
+
// the drift check could not see this unit because the unit was in no list.
|
|
368
|
+
//
|
|
369
|
+
// BEING IN THIS LIST IS NOT YET DETECTION, AND SAYING SO IS THE POINT OF THIS PARAGRAPH. Every name
|
|
370
|
+
// here is bare, and `systemctl show <bare>` answers for the `.service`. For a timer-driven unit that
|
|
371
|
+
// service is `inactive` BETWEEN RUNS and `inactive` when its timer has been stopped — the same
|
|
372
|
+
// answer to both questions. So the health check can now say this unit exists and cannot say whether
|
|
373
|
+
// anything still starts it, which is the failure the entry was written for.
|
|
374
|
+
//
|
|
375
|
+
// The signal lives on `clearotron-doctrine-sync.timer`, whose ActiveState separates `active`
|
|
376
|
+
// (waiting) from `inactive` (stopped). What is missing is not the NAME — `systemdUnits` below
|
|
377
|
+
// carries it — but a consumer: the check derives its queries from the bare unit rather than from
|
|
378
|
+
// that field, so the timer is written down and never asked about.
|
|
379
|
+
//
|
|
380
|
+
// WHEN THE CHECK DERIVES ITS QUERIES FROM `systemdUnits` RATHER THAN FROM THE BARE NAME, THIS
|
|
381
|
+
// PARAGRAPH IS OBSOLETE — and the field below is what it will read. Stated as a condition rather
|
|
382
|
+
// than as a pointer to where it is being done, because a condition is self-verifying: a reader
|
|
383
|
+
// confirms or refutes it by looking at the check today, and the day the derivation lands this reads
|
|
384
|
+
// as history instead of as an open item.
|
|
385
|
+
untrackedReason: "the doctrine refresh on production, timer-driven, running from no file in "
|
|
386
|
+
+ "driver/systemd/ and named nowhere in this repo. It CAN be tracked — it carries no "
|
|
387
|
+
+ "identity-edge value — and nobody wrote it down. Until it is, its absence is at least no "
|
|
388
|
+
+ "longer silent.",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
unit: "client-access", runsOn: [],
|
|
392
|
+
orphanReason: "NOT THIS REPOSITORY'S SERVICE, and no longer running under this name: the legacy "
|
|
393
|
+
+ "account holds it only as `client-access.service.retired-20260904`, a suffix systemd never reads. "
|
|
394
|
+
+ "Declared here so a unit sharing a machine with ours is not invisible, and now declared as gone.",
|
|
395
|
+
measured: "2026-09-08: present on the legacy account ONLY as a retired-suffixed filename, which "
|
|
396
|
+
+ "systemd does not load. No loadable unit of this name under any account.", tracked: null,
|
|
293
397
|
untrackedReason: "it is not this repo's service. Its ExecStart runs a script from a different "
|
|
294
398
|
+ "product's checkout on the same box, not under this clone — so there is no code here to "
|
|
295
399
|
+ "template, and the old reason (CF Access values inline) would send the next reader hunting for a "
|
|
@@ -453,6 +557,59 @@ export const CHECKED_UNITS = Object.freeze(
|
|
|
453
557
|
UNIT_INVENTORY.filter((u) => u.runsOn.length > 0).map((u) => u.unit),
|
|
454
558
|
);
|
|
455
559
|
|
|
560
|
+
/**
|
|
561
|
+
* The TIMER units this deployment claims to have, by name, as systemd spells them.
|
|
562
|
+
*
|
|
563
|
+
* ── WHY A SECOND LIST AND NOT MORE NAMES IN THE ONE ABOVE ───────────────────────────────────────────
|
|
564
|
+
*
|
|
565
|
+
* `CHECKED_UNITS` holds BARE names, and that is load-bearing rather than an accident: the verdict
|
|
566
|
+
* appends the suffixes itself when matching what a box is running, so one entry answers for a unit's
|
|
567
|
+
* service, timer and path alike. The health check then asks `systemctl show <bare>`, and systemd
|
|
568
|
+
* resolves a bare name to the `.service`.
|
|
569
|
+
*
|
|
570
|
+
* For a timer-driven unit that is the wrong question, and it is wrong in the direction that hides the
|
|
571
|
+
* failure. The service of a timer-driven unit reads `ActiveState=inactive` **between runs** — it is
|
|
572
|
+
* meant to — and it reads `inactive` when its timer has been STOPPED. Identical strings, opposite
|
|
573
|
+
* meanings, and the check could report the unit exists while being unable to say whether anything
|
|
574
|
+
* still starts it. That is the whole of what a timer entry is for.
|
|
575
|
+
*
|
|
576
|
+
* The signal lives on the `.timer`, whose `ActiveState` separates `active` (armed and waiting) from
|
|
577
|
+
* `inactive` (stopped, and nothing will fire the service again).
|
|
578
|
+
*
|
|
579
|
+
* ── WHERE THE NAMES COME FROM ───────────────────────────────────────────────────────────────────────
|
|
580
|
+
*
|
|
581
|
+
* From `tracked`, which already lists the files an entry accounts for, so a shipped timer is covered
|
|
582
|
+
* with nothing new to declare and cannot be forgotten. An UNTRACKED unit has no file list to read, so
|
|
583
|
+
* it says so with `systemdUnits:` — the systemd names to ask about, which is a different fact from the files
|
|
584
|
+
* the repository ships and is why it is not folded into `tracked`. Claiming a tracked file that does
|
|
585
|
+
* not exist is a fault; naming a unit this deployment runs is a claim about a box.
|
|
586
|
+
*/
|
|
587
|
+
export const CHECKED_TIMERS = Object.freeze(
|
|
588
|
+
UNIT_INVENTORY.filter((u) => u.runsOn.length > 0)
|
|
589
|
+
.flatMap((u) => (u.systemdUnits ?? u.tracked ?? []).filter((f) => f.endsWith(".timer")))
|
|
590
|
+
.sort(),
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* Judge the timers. PURE — the caller reads systemd.
|
|
595
|
+
*
|
|
596
|
+
* `inactive` is the finding here, which inverts the reflex a reader brings from services. A stopped
|
|
597
|
+
* timer breaks nothing now and nothing fails: the service simply never fires again, and every surface
|
|
598
|
+
* that reports on the service keeps saying `inactive`, which is what it says when the timer is working.
|
|
599
|
+
*/
|
|
600
|
+
export function timerVerdict(timers, { probeFailed = null } = {}) {
|
|
601
|
+
if (probeFailed) return { state: "unknown", stopped: [], absent: [], message: `could not ask systemd about the timers: ${probeFailed}. That is this check failing to look, not a report about them.` };
|
|
602
|
+
const rows = timers ?? [];
|
|
603
|
+
if (!rows.length) return { state: "pass", stopped: [], absent: [], message: "no timer is declared for this box" };
|
|
604
|
+
const absent = rows.filter((t) => t.load === "not-found").map((t) => t.unit).sort();
|
|
605
|
+
const stopped = rows.filter((t) => t.load !== "not-found" && t.active !== "active").map((t) => t.unit).sort();
|
|
606
|
+
const parts = [`${rows.length} declared timer(s)`];
|
|
607
|
+
if (stopped.length) parts.push(`STOPPED: ${stopped.join(", ")} — the service each one drives will not fire again, and its own state stays 'inactive' either way`);
|
|
608
|
+
if (absent.length) parts.push(`${absent.length} declared timer(s) do not exist on this box`);
|
|
609
|
+
if (!stopped.length && !absent.length) parts.push("all armed");
|
|
610
|
+
return { state: stopped.length ? "fail" : "pass", stopped, absent, message: parts.join("; ") };
|
|
611
|
+
}
|
|
612
|
+
|
|
456
613
|
/** Every tracked file the inventory accounts for, flattened. */
|
|
457
614
|
export const ACCOUNTED_FILES = Object.freeze(
|
|
458
615
|
UNIT_INVENTORY.flatMap((u) => u.tracked ?? []),
|
package/driver/verify.mjs
CHANGED
|
@@ -61,7 +61,7 @@ function readRunProfile(p) {
|
|
|
61
61
|
|
|
62
62
|
// ── doc 50: the run-scoped FROZEN framework manifest ────────────────────────────────────────────────
|
|
63
63
|
// _driver/framework.json is the framework-in-force's manifest, frozen beside profile.json at
|
|
64
|
-
// attachProfile time — the band vocabulary every v4 gate joins against (
|
|
64
|
+
// attachProfile time — the band vocabulary every v4 gate joins against (a customer ladder's "Medium" vs house
|
|
65
65
|
// "Moderate" stays consistent within a run and across resume). Same walk-up + fail-closed convention as
|
|
66
66
|
// readRunProfile: absent ⇒ null (archived/legacy runs); unreadable ⇒ invalid (driver-written, so a
|
|
67
67
|
// corrupt one is a bug that must surface, never a silent downgrade to the wrong vocabulary).
|
|
@@ -157,7 +157,7 @@ export async function seatReason({ runDir, runId, assumption, ratedUnder, findin
|
|
|
157
157
|
const replyPath = join(runDir, MEMO_DIR, `reply-${sha(`${runId}${assumption}`).slice(0, 10)}.json`);
|
|
158
158
|
mkdirSync(join(runDir, MEMO_DIR), { recursive: true });
|
|
159
159
|
const r = await dispatch("whatif-memo", {
|
|
160
|
-
message: composeMemoMessage({ assumption, findings, ratedUnder, skill }),
|
|
160
|
+
message: composeMemoMessage({ assumption, findings, ratedUnder, skill, replyPath }),
|
|
161
161
|
model,
|
|
162
162
|
// A BOUNDED READING, and the ceiling says so. This re-reads evidence already gathered; a memo that
|
|
163
163
|
// needed the clearance's own ceiling would not be the cheap thing the plan promised the reader.
|
|
@@ -230,7 +230,12 @@ export function validateMemoReply(raw) {
|
|
|
230
230
|
* The findings are handed over as the run's own JSON rather than summarised: a memo that reasoned over
|
|
231
231
|
* our paraphrase of the evidence would be answering about the paraphrase.
|
|
232
232
|
*/
|
|
233
|
-
|
|
233
|
+
// `ratedUnder` DEFAULTS HERE AND IS REFUSED BY `composeMemo`, and that asymmetry is only safe while the
|
|
234
|
+
// artifact is the gate. One resolution is threaded to both, and nothing reaches disk without the memo
|
|
235
|
+
// composing — so a forgotten field is caught there. It stops being safe the moment a dispatch path calls
|
|
236
|
+
// this composer alone: the seat would be instructed "house default" with no artifact refusal behind it,
|
|
237
|
+
// because no artifact would be composed. A caller that dispatches without composing owes its own check.
|
|
238
|
+
export function composeMemoMessage({ assumption, findings, ratedUnder = null, skill = "", replyPath = null } = {}) {
|
|
234
239
|
return [
|
|
235
240
|
skill.trim(),
|
|
236
241
|
"",
|
|
@@ -250,8 +255,21 @@ export function composeMemoMessage({ assumption, findings, ratedUnder = null, sk
|
|
|
250
255
|
JSON.stringify(findings ?? null, null, 2),
|
|
251
256
|
"```",
|
|
252
257
|
"",
|
|
253
|
-
|
|
254
|
-
|
|
258
|
+
// NAME THE FILE, IN THE FIRST MESSAGE. The skill says what to write and the harness was told where
|
|
259
|
+
// to look, and until this line nothing told the SEAT where to put it. A seat given a write grant and
|
|
260
|
+
// no destination writes somewhere reasonable of its own choosing — an archived run carries one such
|
|
261
|
+
// reply at its run root — and the harness then looks where it asked, does not find it, and fails the
|
|
262
|
+
// attempt on `missing_file`. The warm patch names the path, which is the whole reason the retry
|
|
263
|
+
// succeeded: the second attempt was the first one that said where.
|
|
264
|
+
//
|
|
265
|
+
// The cost of not saying it was never the failure. It was that every memo billed two dispatches for
|
|
266
|
+
// one document and left a retried stage on the parent run's ledger — an INVESTIGATE line on a report
|
|
267
|
+
// that had already been delivered clean.
|
|
268
|
+
replyPath
|
|
269
|
+
? `Write it to this ABSOLUTE path (create parent dirs if needed): ${replyPath}`
|
|
270
|
+
: "Write the JSON object the skill dictates, and nothing else.",
|
|
271
|
+
replyPath ? "Write the JSON object the skill dictates, and nothing else." : null,
|
|
272
|
+
].filter((l) => l !== null).join("\n");
|
|
255
273
|
}
|
|
256
274
|
|
|
257
275
|
/**
|
|
@@ -319,6 +337,10 @@ export async function askArchivedRun({ runId, question, requestedBy = null } = {
|
|
|
319
337
|
body: String(reading?.body ?? ""),
|
|
320
338
|
limits: Array.isArray(reading?.limits) ? reading.limits : [],
|
|
321
339
|
mark: run.markName ?? null,
|
|
340
|
+
// The authority the seat was instructed to assess under, carried to the artifact rather than ending
|
|
341
|
+
// at the return value. `parentRatedUnder` answers null for a run that froze no profile, which is a
|
|
342
|
+
// fact the memo states rather than omits.
|
|
343
|
+
ratedUnder,
|
|
322
344
|
});
|
|
323
345
|
if (!composed.ok)
|
|
324
346
|
return { ok: false, fail: MEMO_FAILS.COMPOSE_REFUSED, detail: composed.reason, missing: composed.missing };
|
package/driver/whatif-memo.mjs
CHANGED
|
@@ -43,10 +43,25 @@ export const REQUIRED = Object.freeze(["assumption", "parentRunId", "parentRepor
|
|
|
43
43
|
* needs new evidence" — it is a claim, and `limitsStated` records which claim was made rather than
|
|
44
44
|
* leaving a reader to infer it from silence.
|
|
45
45
|
*/
|
|
46
|
-
export function composeMemo({ assumption, parentRunId, parentReport, date, body, limits = [], mark = null } = {}) {
|
|
46
|
+
export function composeMemo({ assumption, parentRunId, parentReport, date, body, limits = [], mark = null, ratedUnder } = {}) {
|
|
47
47
|
const missing = REQUIRED.filter((k) => !String({ assumption, parentRunId, parentReport, date, body }[k] ?? "").trim());
|
|
48
48
|
if (missing.length) return { ok: false, missing, reason: `a memo cannot be composed without: ${missing.join(", ")}` };
|
|
49
49
|
|
|
50
|
+
// THE RATING AUTHORITY IS REQUIRED BY PRESENCE, NOT BY TRUTHINESS, because `null` is a real answer.
|
|
51
|
+
// A memo is reasoned under an authority — the seat is instructed to assess under a named framework, or
|
|
52
|
+
// told the run froze none and to stay with the house default — and until this line the only thing that
|
|
53
|
+
// reached disk was the reasoning. A lawyer may act on this document; it has to say what it was rated
|
|
54
|
+
// under.
|
|
55
|
+
//
|
|
56
|
+
// Three states, and the third is the one worth separating. A named key is one fact. The house default
|
|
57
|
+
// is a DIFFERENT fact, not an absence, and it is recorded rather than left blank. A caller that never
|
|
58
|
+
// resolved an authority at all is neither, and refusing it is the point: a memo whose artifact is
|
|
59
|
+
// silent about its authority is exactly the defect this closes, so it must not be composable.
|
|
60
|
+
if (ratedUnder === undefined)
|
|
61
|
+
return { ok: false, missing: ["ratedUnder"],
|
|
62
|
+
reason: "a memo cannot be composed without its rating authority — pass the resolved key, or null "
|
|
63
|
+
+ "for a run that froze no customer profile. Absent is not the same fact as either." };
|
|
64
|
+
|
|
50
65
|
const limitLines = limits
|
|
51
66
|
.map((l) => ({ cannot: String(l?.cannot ?? "").trim(), smallestSearch: String(l?.smallestSearch ?? "").trim() }))
|
|
52
67
|
.filter((l) => l.cannot);
|
|
@@ -64,6 +79,9 @@ export function composeMemo({ assumption, parentRunId, parentReport, date, body,
|
|
|
64
79
|
"",
|
|
65
80
|
`**Date:** ${date}`,
|
|
66
81
|
`**Derived from:** ${parentReport} (run ${parentRunId})`,
|
|
82
|
+
`**Rated under:** ${ratedUnder === null
|
|
83
|
+
? "the house default — this report's run froze no customer profile"
|
|
84
|
+
: String(ratedUnder).trim()}`,
|
|
67
85
|
"",
|
|
68
86
|
"## The assumption you asked me to apply",
|
|
69
87
|
"",
|
package/examples/README.md
CHANGED
|
@@ -26,8 +26,8 @@ surfaces as a failing test rather than as a wrong report.
|
|
|
26
26
|
(`loadGrants` in `shared/scope.mjs`) and asserted to grant what it looks like it grants — `../INSTALL.md`
|
|
27
27
|
§8 tells an installer to copy this file, and a row that silently resolves to nothing means an empty
|
|
28
28
|
world on their first sign-in. **Read it as a shape, not as a starting roster.** Most of the accounts it
|
|
29
|
-
names
|
|
30
|
-
|
|
29
|
+
names are invented and resolve to nothing on a fresh install; the one account a fresh install
|
|
30
|
+
actually has is `demo-brand-owner`. Replace the names with
|
|
31
31
|
your own accounts and keep the shapes: a tenant with every account, a tenant with one, a tenant whose
|
|
32
32
|
users see different subsets.
|
|
33
33
|
|
|
@@ -5,27 +5,26 @@
|
|
|
5
5
|
"users": {
|
|
6
6
|
"principal@firm.example": "*",
|
|
7
7
|
"associate@firm.example": [
|
|
8
|
-
"
|
|
8
|
+
"demo-brand-owner"
|
|
9
9
|
]
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
|
-
"
|
|
12
|
+
"brand-owner-direct": {
|
|
13
13
|
"accounts": [
|
|
14
|
-
"
|
|
14
|
+
"demo-brand-owner"
|
|
15
15
|
],
|
|
16
16
|
"users": {
|
|
17
|
-
"*@
|
|
17
|
+
"*@brand-owner.example": "*"
|
|
18
18
|
}
|
|
19
19
|
},
|
|
20
20
|
"evaluation": {
|
|
21
21
|
"accounts": [
|
|
22
|
-
"
|
|
23
|
-
"petcary"
|
|
22
|
+
"demo-brand-owner"
|
|
24
23
|
],
|
|
25
24
|
"users": {
|
|
26
25
|
"reviewer@partner.example": "*",
|
|
27
26
|
"observer@partner.example": [
|
|
28
|
-
"
|
|
27
|
+
"demo-brand-owner"
|
|
29
28
|
]
|
|
30
29
|
}
|
|
31
30
|
},
|
package/mcp-server/CHANGELOG.md
CHANGED
package/mcp-server/lib/scrub.mjs
CHANGED
|
@@ -54,7 +54,7 @@ export const CLIENT_FRONT_MATTER = new Set([
|
|
|
54
54
|
// `Generic default (generic) · Generic default framework · profile d37721cda899` → drop the profile segment.
|
|
55
55
|
// The framework title is client-facing (report footer: "Rated under <title>"); the profile hash is the
|
|
56
56
|
// internal identity of the config that rated the matter and is not. A custom framework carries its source
|
|
57
|
-
// FILENAME too (`custom framework:
|
|
57
|
+
// FILENAME too (`custom framework: Foxglade Interactive ACP risk framework (risk-framework-foxglade.md)`) — the
|
|
58
58
|
// human title stays, the file is config identity and goes.
|
|
59
59
|
function ratedUnderForClient(v) {
|
|
60
60
|
return String(v ?? "").split("·").map((s) => s.trim())
|
|
@@ -278,8 +278,8 @@ export async function whatIfRun({ confirmationToken } = {}, deps = {}) {
|
|
|
278
278
|
// The reconstruction above carries six fields and resolveProfile keys on none of them. It reads
|
|
279
279
|
// `job.profileKey` first, then falls back to `job.forwarderDomain`; the job has `forwarder` but not
|
|
280
280
|
// `forwarderDomain`, and no profileKey at all. So BOTH resolution routes were dead here and every
|
|
281
|
-
// what-if silently resolved to the house `generic` profile. Measured on a
|
|
282
|
-
// {"event":"profile-mismatch","sidecar":"
|
|
281
|
+
// what-if silently resolved to the house `generic` profile. Measured on a test-account run:
|
|
282
|
+
// {"event":"profile-mismatch","sidecar":"burrowell","resolved":"generic"} — while the client-facing
|
|
283
283
|
// result reported ok:true and "Sandboxed re-run complete" and said nothing.
|
|
284
284
|
//
|
|
285
285
|
// WHY THAT IS WORSE THAN A WRONG LABEL. A what-if changes ONE thing and reads the difference. This
|
package/mcp-server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trademark-artifacts-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"private": true,
|
|
6
6
|
"description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
|
|
@@ -29,8 +29,8 @@ original message. It does not guess.
|
|
|
29
29
|
|
|
30
30
|
## Late applicant binding
|
|
31
31
|
|
|
32
|
-
Requester (mid-run): *"the applicant will be
|
|
33
|
-
customer: "
|
|
32
|
+
Requester (mid-run): *"the applicant will be Coastline Drinks Ltd"* → `feed_context { runId,
|
|
33
|
+
customer: "Coastline Drinks Ltd" }` → the outbox later carries a `late-bind-ack` to relay.
|
|
34
34
|
|
|
35
35
|
## Courier wake
|
|
36
36
|
|
package/mcp-server/server.mjs
CHANGED
|
@@ -232,7 +232,7 @@ const tools = {
|
|
|
232
232
|
projects: (byCustomer.get(p.key) ?? []).sort((a, b) => a.key.localeCompare(b.key)) }))
|
|
233
233
|
.sort((a, b) => a.key.localeCompare(b.key));
|
|
234
234
|
return {
|
|
235
|
-
_note: "Customer roster for intake resolution. Resolve by JUDGMENT — an explicit name, a misspelling
|
|
235
|
+
_note: "Customer roster for intake resolution. Resolve by JUDGMENT — an explicit name, a misspelling of one of the keys below, or an implicit reference (\"our functional-beverage client\") all map to a key. Set the job's profileKey to the chosen customer key; OMIT it for a new/unknown customer (⇒ the neutral generic profile). If the request names a specific PROJECT/engagement under that customer (listed in `projects[]`), also set projectKey to that project's key; OMIT projectKey when no project is meant (⇒ the customer profile). CLARIFY if you cannot tell either. Never pick a profile from the sender's email domain.",
|
|
236
236
|
clients,
|
|
237
237
|
genericFallback: "generic",
|
|
238
238
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clearotron",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.3",
|
|
5
5
|
"license": "AGPL-3.0-only",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"description": "Clearotron — trademark-clearance engine: deterministic orchestration driver + MCP interrogation server. Provider-agnostic, model-agnostic, runs headless (no gateway).",
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=22.
|
|
16
|
+
"node": ">=22.13.0"
|
|
17
17
|
},
|
|
18
18
|
"workspaces": [
|
|
19
19
|
"driver",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"portal-ui"
|
|
23
23
|
],
|
|
24
24
|
"scripts": {
|
|
25
|
+
"preinstall": "node scripts/preinstall-node-check.mjs",
|
|
25
26
|
"postinstall": "node -e \"const{existsSync}=require('node:fs');const{execFileSync}=require('node:child_process');if(existsSync('scripts/install-hooks.mjs'))execFileSync(process.execPath,['scripts/install-hooks.mjs'],{stdio:'inherit'})\"",
|
|
26
27
|
"prepack": "node scripts/write-build-info.mjs",
|
|
27
28
|
"prepublishOnly": "node scripts/release-completeness-check.mjs",
|
|
@@ -72,7 +73,7 @@
|
|
|
72
73
|
"buffers": "file:vendor/buffers",
|
|
73
74
|
"exceljs": "^4.4.0",
|
|
74
75
|
"jose": "^6.2.3",
|
|
75
|
-
"undici": "^
|
|
76
|
+
"undici": "^7.29.1"
|
|
76
77
|
},
|
|
77
78
|
"bin": {
|
|
78
79
|
"clearotron": "bin/clearotron.mjs"
|
|
@@ -105,13 +106,30 @@
|
|
|
105
106
|
"portal-ui/dist/",
|
|
106
107
|
"portal-ui/package.json",
|
|
107
108
|
".env.example",
|
|
108
|
-
"!driver/profiles
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
"!driver/profiles
|
|
112
|
-
"!driver/profiles/projects
|
|
113
|
-
"
|
|
114
|
-
"!driver/recipes
|
|
109
|
+
"!driver/profiles/*.json",
|
|
110
|
+
"driver/profiles/generic.json",
|
|
111
|
+
"driver/profiles/demo-brand-owner.json",
|
|
112
|
+
"!driver/profiles/*.context.md",
|
|
113
|
+
"!driver/profiles/projects/*/",
|
|
114
|
+
"driver/profiles/projects/demo-brand-owner/",
|
|
115
|
+
"!driver/recipes/*/",
|
|
116
|
+
"!driver/skills/prelim-search/risk-framework-*",
|
|
117
|
+
"driver/skills/prelim-search/risk-framework.md",
|
|
118
|
+
"driver/skills/prelim-search/risk-framework.manifest.json",
|
|
119
|
+
"driver/skills/prelim-search/risk-framework-demo.md",
|
|
120
|
+
"driver/skills/prelim-search/risk-framework-demo.manifest.json",
|
|
121
|
+
"driver/skills/prelim-search/risk-framework-triage.md",
|
|
122
|
+
"driver/skills/prelim-search/risk-framework-triage.manifest.json",
|
|
123
|
+
"!driver/skills/prelim-search/worked-examples-*",
|
|
124
|
+
"driver/skills/prelim-search/worked-examples.md",
|
|
125
|
+
"driver/skills/prelim-search/worked-examples-demo.md",
|
|
126
|
+
"!scripts/home-render-check.mjs",
|
|
127
|
+
"!scripts/ai-page-render-check.mjs",
|
|
128
|
+
"!scripts/composer-render-check.mjs",
|
|
129
|
+
"!scripts/clearances-render-check.mjs",
|
|
130
|
+
"!scripts/portal-lifecycle-check.mjs",
|
|
131
|
+
"!scripts/test-account-names.mjs",
|
|
132
|
+
"!scripts/no-test-account-reaches-the-package.mjs",
|
|
115
133
|
"!**/test/",
|
|
116
134
|
"!**/bench/",
|
|
117
135
|
"!**/*.test.mjs",
|