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
package/bin/start.mjs
CHANGED
|
@@ -130,6 +130,9 @@ import { productIdentity } from "../shared/product-identity.mjs"; // AGPL §13
|
|
|
130
130
|
import { pinEnvAll } from "../shared/env-aliases.mjs"; // — a pin that names one spelling has set nothing that wins
|
|
131
131
|
import { BRAND } from "../shared/brand.mjs"; // — the installer's own name, from the tenant seam
|
|
132
132
|
import { rebuildIfStale } from "../shared/bundle-rebuild.mjs"; // never serve a bundle older than its sources
|
|
133
|
+
// ONE CLASSIFIER FOR WHAT A STAFF RULE ADMITS, shared with the setup wizard. Two copies of this
|
|
134
|
+
// judgement would be a wizard that asks about one rule and a launcher that writes another.
|
|
135
|
+
import { classifyStaffDomain, staffDomainRefusal, staffGrantSentence } from "../shared/staff-domain.mjs";
|
|
133
136
|
|
|
134
137
|
const REPO = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
135
138
|
const ENV_PATH = envLocalPath({ repoRoot: REPO }); // resolved, never composed: one resolver, so moving this file later is one line
|
|
@@ -366,6 +369,11 @@ export function installPaths(base) {
|
|
|
366
369
|
};
|
|
367
370
|
}
|
|
368
371
|
|
|
372
|
+
/** A staff rule this command will not build. Carries the sentence the operator reads, and why. */
|
|
373
|
+
export class StaffDomainRefused extends Error {
|
|
374
|
+
constructor(domain, message) { super(message); this.name = "StaffDomainRefused"; this.domain = domain; }
|
|
375
|
+
}
|
|
376
|
+
|
|
369
377
|
/**
|
|
370
378
|
* The one address that signs in, and the staff domain derived from it.
|
|
371
379
|
*
|
|
@@ -374,10 +382,61 @@ export function installPaths(base) {
|
|
|
374
382
|
* it safe here and would not be safe on a hosted instance. Without it the sign-in succeeds and every
|
|
375
383
|
* page 403s, because signing in is not being enrolled (portal-access.mjs decides that, and it is
|
|
376
384
|
* deliberately blind to which door you came through).
|
|
385
|
+
*
|
|
386
|
+
* ── AND THAT PARAGRAPH IS ONLY TRUE OF `<account>@localhost` ─────────────────────────────────────────
|
|
387
|
+
*
|
|
388
|
+
* "admits exactly that address and nobody else" is a property of the DOMAIN, not of the mode. It holds
|
|
389
|
+
* for `localhost`. It does not hold for a domain other people have addresses at: the rule this line
|
|
390
|
+
* produces from `alex@a-firm.example-tld` says *anyone at that domain*, the settings page then reports
|
|
391
|
+
* it back in those words, and on a deployment where identity arrives from an external login system the
|
|
392
|
+
* domain is the whole of the check. Nobody was asked, and there is nothing on the box recording that a
|
|
393
|
+
* grant was made.
|
|
394
|
+
*
|
|
395
|
+
* So the derivation now refuses the domains that can never be a staff rule — a webmail provider, a
|
|
396
|
+
* documentation domain — and hands anything wider than one machine back to the caller to state and
|
|
397
|
+
* confirm. `shared/staff-domain.mjs` is the one classifier, shared with the setup wizard, so the
|
|
398
|
+
* question the wizard asks and the rule this command builds cannot drift apart.
|
|
399
|
+
*
|
|
400
|
+
* An UNPARSEABLE address still returns `""`, and that is not the same answer as a refusal: nothing was
|
|
401
|
+
* classified, so nothing was decided. The caller already refuses a non-address one line earlier.
|
|
377
402
|
*/
|
|
378
403
|
export function staffDomainFor(email) {
|
|
379
404
|
const at = String(email ?? "").lastIndexOf("@");
|
|
380
|
-
|
|
405
|
+
const domain = at > 0 ? String(email).slice(at + 1).toLowerCase() : "";
|
|
406
|
+
if (!domain) return "";
|
|
407
|
+
const refusal = staffDomainRefusal(domain);
|
|
408
|
+
if (refusal) throw new StaffDomainRefused(domain, refusal);
|
|
409
|
+
return domain;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* What to say when the derived domain is a real one — wider than this machine, and never assumed.
|
|
414
|
+
*
|
|
415
|
+
* A REFUSAL RATHER THAN A PROMPT, and the reason is that this command has no reader. `start` runs from
|
|
416
|
+
* a script, from `--background`, and from a service manager as often as from a terminal; a question
|
|
417
|
+
* asked there either hangs or is answered by whatever happens to be on stdin. Every other decision on
|
|
418
|
+
* this boundary already refuses instead — an unset access file, a multi-`@` identity, local mode off
|
|
419
|
+
* loopback — so this is the idiom the rest of the door speaks.
|
|
420
|
+
*
|
|
421
|
+
* The consent itself lives where a person is definitionally present: `clearotron install` asks for the
|
|
422
|
+
* address, shows this same sentence, and writes the answer down as an explicit `PORTAL_STAFF_DOMAINS`.
|
|
423
|
+
* After that the `||` above this call short-circuits and the derivation is never reached again.
|
|
424
|
+
*/
|
|
425
|
+
export function wideStaffDomainRefusal({ user, domain, envPath }) {
|
|
426
|
+
return `${user} would make every address at ${domain} an administrator of this install.\n`
|
|
427
|
+
+ "\n"
|
|
428
|
+
+ ` The rule that would be written is: ${staffGrantSentence(domain, { staffLabel: `${BRAND.name} staff` })}\n`
|
|
429
|
+
+ "\n"
|
|
430
|
+
+ " That is a grant to a group, and this command will not make one on your behalf. Pick the one\n"
|
|
431
|
+
+ " that is true here:\n"
|
|
432
|
+
+ "\n"
|
|
433
|
+
+ ` · only you use this machine — start without --user, or use ${String(user).slice(0, String(user).lastIndexOf("@"))}@localhost.\n`
|
|
434
|
+
+ ` The rule is then this machine and nobody else, and nothing is granted to anyone.\n`
|
|
435
|
+
+ ` · your colleagues at ${domain} should all be administrators — say so once, in writing:\n`
|
|
436
|
+
+ ` PORTAL_STAFF_DOMAINS=${domain}\n`
|
|
437
|
+
+ ` in ${envPath}, or in the environment. The settings page then names that file as where the\n`
|
|
438
|
+
+ " rule came from, so whoever reads it later can undo it.\n"
|
|
439
|
+
+ ` · run \`${invocationPrefix()}clearotron install\`, which asks for the address and this question with it.`;
|
|
381
440
|
}
|
|
382
441
|
|
|
383
442
|
/**
|
|
@@ -777,6 +836,10 @@ if (isMain) {
|
|
|
777
836
|
// bound anywhere else in any mode. Sign-in is untouched — the demo signs in like any first start, and
|
|
778
837
|
// the portal mints and prints its passphrase exactly as it does for a real one.
|
|
779
838
|
const DEMO = argv.includes("--demo");
|
|
839
|
+
// THE DEMO BRINGS ITS OWN ACCOUNT. A fresh install resolves `generic` and nothing else (owner
|
|
840
|
+
// ruling, 2026-09-08), so the demo account is refused from the roster unless somebody asked for it.
|
|
841
|
+
// Asked here, once and visibly, rather than at each site that happens to read a roster.
|
|
842
|
+
if (DEMO) process.env.CLEAROTRON_DEMO_PROFILES ??= "1";
|
|
780
843
|
// ── `--port` MOVES EVERY DOOR IT OPENS (tracker issue 166) ───────────────────────────────────────
|
|
781
844
|
//
|
|
782
845
|
// It used to move ONE of the three. `resolvePorts` reads three independent variables with three
|
|
@@ -812,16 +875,47 @@ if (isMain) {
|
|
|
812
875
|
run: (cmd, args) => spawnSync(cmd, args, { cwd: REPO, stdio: "inherit", shell: process.platform === "win32" }).status ?? 1,
|
|
813
876
|
});
|
|
814
877
|
|
|
815
|
-
//
|
|
816
|
-
// the address never leaves the machine — `--user` is there for a reader who wants their real
|
|
817
|
-
// whatever is resolved here is written to `.env` so the question is never put twice.
|
|
878
|
+
// The ADDRESS is not asked for here: a local install has one user, this machine already knows their
|
|
879
|
+
// name, and the address never leaves the machine — `--user` is there for a reader who wants their real
|
|
880
|
+
// one, and whatever is resolved here is written to `.env` so the question is never put twice. What IS
|
|
881
|
+
// asked for, and used not to be, is the staff rule the address implies once it is a real one; that is
|
|
882
|
+
// the block below the address, and `clearotron install` is where the question is actually put.
|
|
818
883
|
let whoami = "user";
|
|
819
884
|
try { whoami = userInfo().username || "user"; } catch { /* a container with no passwd entry */ }
|
|
820
885
|
// In a demo the address is the demo's own and is never written anywhere: see the DEMO block above.
|
|
821
886
|
const user = String(flag("--user", DEMO ? "demo@localhost" : (process.env.PORTAL_LOCAL_USER || `${whoami}@localhost`))).trim().toLowerCase();
|
|
822
887
|
if (!user.includes("@") || user.indexOf("@") !== user.lastIndexOf("@"))
|
|
823
888
|
fatal(`--user "${user}" is not a single email address. It is the one identity that signs in here, and the portal refuses a multi-@ identity outright.`);
|
|
824
|
-
|
|
889
|
+
// ── THE STAFF RULE THIS ADDRESS IMPLIES, STATED BEFORE IT IS WRITTEN ──────────────────────────────
|
|
890
|
+
//
|
|
891
|
+
// An explicit `PORTAL_STAFF_DOMAINS` is a decision somebody already made in writing, and it wins
|
|
892
|
+
// untouched — including a domain the classifier would otherwise refuse, because an operator who
|
|
893
|
+
// typed it has said what they mean and this command does not overrule that.
|
|
894
|
+
//
|
|
895
|
+
// Absent one, the rule is DERIVED from the single address, and that derivation is the defect this
|
|
896
|
+
// block exists to close. Three outcomes:
|
|
897
|
+
//
|
|
898
|
+
// `localhost` (or any bare hostname) — one machine, no second person, nothing to ask. Silent, as
|
|
899
|
+
// it has always been. This is the default path and the only
|
|
900
|
+
// one a laptop ever reaches.
|
|
901
|
+
// a webmail or documentation domain — refused by `staffDomainFor` itself; there is no yes that
|
|
902
|
+
// makes it right.
|
|
903
|
+
// any other real domain — refused HERE, with the rule quoted in the words the
|
|
904
|
+
// settings page uses and the three ways out named.
|
|
905
|
+
//
|
|
906
|
+
// BEFORE THIS RUN CHANGES THE BOX, deliberately — it sits above the state-written divide further
|
|
907
|
+
// down, with the units gate, the auth-mode gate and the port probe. A refused start has written no
|
|
908
|
+
// `.env`, minted no secret and placed no unit, so the reader's only question — is my install
|
|
909
|
+
// half-made — has one answer.
|
|
910
|
+
let staffDomains = process.env.PORTAL_STAFF_DOMAINS;
|
|
911
|
+
if (!staffDomains) {
|
|
912
|
+
let derived = "";
|
|
913
|
+
try { derived = staffDomainFor(user); }
|
|
914
|
+
catch (e) { fatal(e.message); }
|
|
915
|
+
if (derived && classifyStaffDomain(derived) === "wide")
|
|
916
|
+
fatal(wideStaffDomainRefusal({ user, domain: derived, envPath: ENV_PATH }));
|
|
917
|
+
staffDomains = derived;
|
|
918
|
+
}
|
|
825
919
|
|
|
826
920
|
say("");
|
|
827
921
|
say(` ${BRAND.name} ${BRAND.product.toLowerCase()} — local install`);
|
|
@@ -1574,10 +1668,40 @@ if (isMain) {
|
|
|
1574
1668
|
// sequence rather than a race between the terminal's signal and ours. It also means the group
|
|
1575
1669
|
// kill below reaches anything a child spawns, not just the child.
|
|
1576
1670
|
detached: true,
|
|
1577
|
-
|
|
1671
|
+
// — STDERR IS TEED RATHER THAN INHERITED, AND ONLY STDERR.
|
|
1672
|
+
//
|
|
1673
|
+
// These children announce a refusal as one FATAL line on stderr and exit 1. With `inherit` the
|
|
1674
|
+
// parent holds no copy, so the failure message below could only point at output it had never seen —
|
|
1675
|
+
// and on one report that output never reached the reader's terminal at all. It said "its own output
|
|
1676
|
+
// above says why" about a line that was not above, which is worse than saying nothing: it sends
|
|
1677
|
+
// somebody scrolling for a sentence they will not find.
|
|
1678
|
+
//
|
|
1679
|
+
// stdout stays inherited. It carries no refusal — measured: on a refusing door stdout is empty and
|
|
1680
|
+
// stderr holds the whole of it — and leaving it alone keeps the child's terminal detection and any
|
|
1681
|
+
// progress rendering intact. Piping a stream costs the child its TTY, so only the stream that has to
|
|
1682
|
+
// be read is piped.
|
|
1683
|
+
stdio: ["ignore", "inherit", "pipe"],
|
|
1578
1684
|
env: { ...process.env, ...env },
|
|
1579
1685
|
});
|
|
1580
|
-
|
|
1686
|
+
// The last lines of that child's stderr, forwarded on as they arrive so nothing is delayed or
|
|
1687
|
+
// swallowed, and kept so the failure can QUOTE them rather than refer to them.
|
|
1688
|
+
const tail = [];
|
|
1689
|
+
// A CHUNK IS NOT A LINE. `data` arrives on whatever boundary the pipe gives us, so a stream split
|
|
1690
|
+
// mid-sentence would push two half-lines and the quoted last words would reach the reader cut in
|
|
1691
|
+
// half — on precisely the message that exists because they could not see the original. The partial
|
|
1692
|
+
// remainder is held over and completed by the next chunk; whatever is left when the stream ends is
|
|
1693
|
+
// flushed, because a process that dies mid-line still said the thing it was saying.
|
|
1694
|
+
let pending = "";
|
|
1695
|
+
const keep = (line) => { if (line.trim()) tail.push(line); while (tail.length > 12) tail.shift(); };
|
|
1696
|
+
child.stderr?.setEncoding("utf8");
|
|
1697
|
+
child.stderr?.on("data", (chunk) => {
|
|
1698
|
+
process.stderr.write(chunk);
|
|
1699
|
+
const parts = (pending + String(chunk)).split("\n");
|
|
1700
|
+
pending = parts.pop() ?? "";
|
|
1701
|
+
for (const line of parts) keep(line);
|
|
1702
|
+
});
|
|
1703
|
+
child.stderr?.on("end", () => { keep(pending); pending = ""; });
|
|
1704
|
+
const rec = { name, script, child, alive: true, tail };
|
|
1581
1705
|
children.push(rec);
|
|
1582
1706
|
child.on("exit", (code, signal) => {
|
|
1583
1707
|
rec.alive = false;
|
|
@@ -1590,7 +1714,7 @@ if (isMain) {
|
|
|
1590
1714
|
+ ` but nothing is draining the queue now. Start one with:\n node ${script} --watch\n`);
|
|
1591
1715
|
return;
|
|
1592
1716
|
}
|
|
1593
|
-
err(
|
|
1717
|
+
err(childExitReport({ name, script, code, signal, tail }));
|
|
1594
1718
|
void shutdown(1);
|
|
1595
1719
|
});
|
|
1596
1720
|
child.on("error", (e) => {
|
|
@@ -1861,3 +1985,25 @@ if (isMain) {
|
|
|
1861
1985
|
say(" — same product, managed by systemd, and it survives logout.");
|
|
1862
1986
|
say("");
|
|
1863
1987
|
}
|
|
1988
|
+
|
|
1989
|
+
/**
|
|
1990
|
+
* What a reader is told when a child this install cannot run without exits.
|
|
1991
|
+
*
|
|
1992
|
+
* QUOTED, NOT POINTED AT. This used to end "its own output above says why" while the parent inherited
|
|
1993
|
+
* the child's stderr and therefore held no copy of it. On one report that output never reached the
|
|
1994
|
+
* reader's terminal at all, so the sentence sent somebody scrolling for a line that was not there —
|
|
1995
|
+
* worse than saying nothing, because it reads as a working instruction.
|
|
1996
|
+
*
|
|
1997
|
+
* An empty tail is reported as an empty tail. A child that exits silently is a finding about the child,
|
|
1998
|
+
* and printing a heading with nothing under it would hide exactly that.
|
|
1999
|
+
*
|
|
2000
|
+
* PURE, and exported, so this can be driven without starting a supervisor — the failure path of a
|
|
2001
|
+
* process manager is the one nobody exercises by hand.
|
|
2002
|
+
*/
|
|
2003
|
+
export function childExitReport({ name, script, code, signal, tail = [] }) {
|
|
2004
|
+
const how = signal ? `on ${signal}` : `with code ${code}`;
|
|
2005
|
+
const said = tail.length
|
|
2006
|
+
? `Its last line${tail.length > 1 ? "s" : ""}:\n${tail.map((l) => ` ${l}`).join("\n")}\n`
|
|
2007
|
+
: "It exited without printing anything, which is itself the thing to report.\n";
|
|
2008
|
+
return `\nstart: ${name} (${script}) exited ${how}. Stopping the rest.\n${said}`;
|
|
2009
|
+
}
|
package/build-info.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-default",
|
|
4
4
|
"title": "Generic default risk framework",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house risk framework, authored in-house and reviewed by counsel; 2026-08-31 revision",
|
|
6
6
|
"entity_label": "the company",
|
|
7
7
|
"bands": [
|
|
8
8
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-default",
|
|
4
4
|
"title": "Generic default risk framework",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house risk framework, authored in-house and reviewed by counsel; 2026-08-31 revision",
|
|
6
6
|
"entity_label": "the company",
|
|
7
7
|
"bands": [
|
|
8
8
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-triage",
|
|
4
4
|
"title": "House knockout triage ladder (Stage 0)",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house triage scale for knockout screening, authored in-house; five rating tiers",
|
|
6
6
|
"entity_label": "the client",
|
|
7
7
|
"bands": [
|
|
8
8
|
{
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"schema_version": 1,
|
|
3
3
|
"framework_key": "house-default",
|
|
4
4
|
"title": "Generic default risk framework",
|
|
5
|
-
"source_deck": "
|
|
5
|
+
"source_deck": "Cordillera house risk framework, authored in-house and reviewed by counsel; 2026-08-31 revision",
|
|
6
6
|
"entity_label": "the company",
|
|
7
7
|
"bands": [
|
|
8
8
|
{
|
package/docs/E2E.md
CHANGED
|
@@ -45,7 +45,7 @@ CLEAROTRON_DATABASE=corsearch # REQUIRED, no default (track
|
|
|
45
45
|
CORSEARCH_SESSION_KEY=dev-offline # the credential preflight wants it set; never fetches under the mock
|
|
46
46
|
CLEAROTRON_SATPROBE_CODESIDE=0 # the probe dials the provider; a mock run cannot
|
|
47
47
|
CLEAROTRON_BAND_TRUTH_GATE=0 # the gate evidences bands against the production call ledger
|
|
48
|
-
CLEAROTRON_CUSTOMERS_DIR= # unset ⇒ the
|
|
48
|
+
CLEAROTRON_CUSTOMERS_DIR= # unset ⇒ the demo customer bundled with the repo
|
|
49
49
|
# MCP face (optional): TRADEMARK_MCP_DEV=1 TRADEMARK_MCP_AUTH_DISABLED=1 + loopback host + an ABSOLUTE
|
|
50
50
|
# CLEAROTRON_ACCESS_FILE=/abs/path/grants.json. All four, or it refuses to start: with auth off and no
|
|
51
51
|
# grants file every token-less caller resolves to internal read-all across every customer.
|
|
@@ -59,8 +59,8 @@ and the refusal prints the absolute path to write.
|
|
|
59
59
|
Drive one run through the whole spine by hand:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
node driver/enqueue.mjs --mark "
|
|
63
|
-
--forwarder ops --profile
|
|
62
|
+
node driver/enqueue.mjs --mark "NORTHWIND PROBE" --classes 9,41 --goods "water-quality monitoring software" \
|
|
63
|
+
--forwarder ops --profile demo-brand-owner
|
|
64
64
|
node driver/runner.mjs # claims, runs all stages on the mock, publishes, writes the packets
|
|
65
65
|
ls /home/you/trademark-dev/outbox # → <runId>.pending (+ failure/intake packets on the sad paths)
|
|
66
66
|
cat <archived run>/_driver/delivery.json
|
|
@@ -75,7 +75,7 @@ outbox packet kind — on a real filesystem with real process boundaries, for fr
|
|
|
75
75
|
this, from a clean `env -i` shell: enqueue → runner → `DELIVERED (verdict CLEAR)` → archived run dir
|
|
76
76
|
+ `<runId>.pending` outbox marker + `_driver/delivery.json` (runId, forwarder, subject,
|
|
77
77
|
`emailBodyHtml`, url, verdict — no profile field; that is `_driver/profile.json` beside it, the run's
|
|
78
|
-
frozen
|
|
78
|
+
frozen demo profile, and the file to open to prove which profile resolved).
|
|
79
79
|
|
|
80
80
|
### Tier 1b — the UI PORTAL (browse the dev instance; develop UI features against it)
|
|
81
81
|
|
|
@@ -105,7 +105,7 @@ so a dev instance beside a live one must be given its own (`PORTAL_PORT`, `PROFI
|
|
|
105
105
|
silently — each is a proxy to a port, and the port is all it knows. `/recipes/*` is the worse half:
|
|
106
106
|
its save endpoint writes and git-commits into whichever recipe store it reached.
|
|
107
107
|
|
|
108
|
-
A pass here looks like: index, run report, the
|
|
108
|
+
A pass here looks like: index, run report, the demo customer page and the profile-editor UI all
|
|
109
109
|
render against the Tier-1 pool; the `/profiles/*` proxy round-trips; traversal and non-loopback binds
|
|
110
110
|
are refused (unit-tested).
|
|
111
111
|
|
package/docs/PORTAL.md
CHANGED
|
@@ -133,14 +133,14 @@ rather than quietly opening a passphrase door instead; and local mode refuses a
|
|
|
133
133
|
`PORTAL_SERVICE_HOST`, because off loopback the passphrase and the session cookie are on the wire in
|
|
134
134
|
clear. Put a TLS-terminating proxy in front if it has to be reachable.
|
|
135
135
|
|
|
136
|
-
Grants fixture: `{"tenants":{"demo":{"accounts":["
|
|
136
|
+
Grants fixture: `{"tenants":{"demo":{"accounts":["foxglade"],"users":{"cli@celta.example":["foxglade"]}}}}`.
|
|
137
137
|
|
|
138
138
|
The trigger lane needs the MCP HTTP face and an accounts-scoped ops token; without both, the run step
|
|
139
139
|
reports the trigger lane unwired and the plan step still works. **The face no longer has to be run in
|
|
140
140
|
its dev bypass to provide that**:`TRADEMARK_MCP_AUTH_MODE=token` runs it with a mandatory scoped
|
|
141
141
|
access key and no auth proxy — loopback only, and refused outright alongside
|
|
142
142
|
`TRADEMARK_MCP_AUTH_DISABLED`, which authenticates nobody. Mint the key with
|
|
143
|
-
`mint-token.mjs --scope ops --sub portal --verbs start_run,stop_run --accounts
|
|
143
|
+
`mint-token.mjs --scope ops --sub portal --verbs start_run,stop_run --accounts foxglade`, or let
|
|
144
144
|
`npx clearotron start` mint one in memory at every start and never write it down.
|
|
145
145
|
|
|
146
146
|
## Putting your own login provider in front
|
package/docs/README.md
CHANGED
|
@@ -76,10 +76,11 @@ The other half is structural: it fails on any undeclared identity inside a matte
|
|
|
76
76
|
which is the half that catches something new. Without the private table the guard runs on synthetic
|
|
77
77
|
sentinels: the machinery is exercised and there is nothing real to find.
|
|
78
78
|
|
|
79
|
-
**Demo clients are synthetic.**
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
(`CLEAROTRON_CUSTOMERS_DIR`) and are never
|
|
79
|
+
**Demo clients are synthetic.** The published package carries a Generic default (`generic`) and one
|
|
80
|
+
demo brand owner. The repository holds three further invented accounts — gaming, functional drinks and
|
|
81
|
+
animal health — which exercise the per-client machinery and the test suite and are never published.
|
|
82
|
+
Real client bundles load at runtime from a private store (`CLEAROTRON_CUSTOMERS_DIR`) and are never
|
|
83
|
+
committed here.
|
|
83
84
|
|
|
84
85
|
**Real third-party names are deliberate.** Registers, marketplaces, regulators, research providers and
|
|
85
86
|
the parties to published decisions are named throughout the code, tests and fixtures — as facts about
|
package/docs/SECURITY.md
CHANGED
|
@@ -39,6 +39,23 @@ Exactly two roles exist: **staff**, admitted by an email-domain rule (`PORTAL_ST
|
|
|
39
39
|
created in the guest-list file by whoever administers the box — `npm run grant` is the editor for it —
|
|
40
40
|
never from a browser. The file is re-read per request, so a grant lands without a restart.
|
|
41
41
|
|
|
42
|
+
**A staff domain is a grant to everyone at that domain, so nothing derives one for you.** Every address
|
|
43
|
+
at a listed domain that gets past the sign-in door is staff, and staff sees every brand owner on the
|
|
44
|
+
instance. The install therefore derives `PORTAL_STAFF_DOMAINS` only from a sign-in address that names no
|
|
45
|
+
second person — the `<account>@localhost` form a single-user machine gets by default. Given a real
|
|
46
|
+
address it states the rule it would create, in the words the People & access screen will later use,
|
|
47
|
+
and refuses to write it: set `PORTAL_STAFF_DOMAINS` yourself, or answer the question `clearotron install`
|
|
48
|
+
asks. A webmail or shared provider is refused outright, because a rule built from one admits the public.
|
|
49
|
+
The People & access screen names the setting the rule came from and the file it is written in, so a
|
|
50
|
+
rule can be traced and undone by whoever finds it.
|
|
51
|
+
|
|
52
|
+
**That check is on the DERIVATION, not on the value.** A `PORTAL_STAFF_DOMAINS` you set yourself is
|
|
53
|
+
taken as written and never classified: `PORTAL_STAFF_DOMAINS=gmail.com` is accepted, and it admits
|
|
54
|
+
everyone at that provider who gets past the sign-in door. This is deliberate — a value someone typed is
|
|
55
|
+
a decision already taken, and the defect being fixed was a rule created with nobody asked — but it means
|
|
56
|
+
the protection is against the accident and not against the configuration. Read what you set, or set
|
|
57
|
+
nothing and answer the question the install asks.
|
|
58
|
+
|
|
42
59
|
|
|
43
60
|
- **On the proxy door**, every HTTP request re-validates a JWT from the fronting auth proxy. The
|
|
44
61
|
proxy is yours to choose — issuer, JWKS URL, claim and header are config
|
|
@@ -283,6 +283,8 @@ because the rule is about what PRODUCT CODE reads, not about what a run reads.
|
|
|
283
283
|
|---|---|---|
|
|
284
284
|
| `DEMO_PORT` | `18900` | Port `npx clearotron demo` serves the replayed report on. `--port` overrides it. |
|
|
285
285
|
| `CLEAROTRON_DEMO` | unset | `1` puts this install in the DEMO posture. **Ordering is real**: the four products are listed and orderable, the form, the plan and the confirmation are the product's own, and the confirmation resolves to a finished report that already exists rather than dispatching — no engine turn, no register call, no queue entry, no run directory (owner ruling 2026-08-31, superseding the greyed-control ruling of the same day). A product the demo carries no finished report for refuses and names which one. It also re-aims two boot warnings written for an operator of a real deployment at the visitor who is not one, from one place (`driver/demo-posture.mjs`). **Set by `npx clearotron demo`, not by an operator** — it is passed explicitly to the two processes that have a reason to know (the portal and the MCP door; the worker is not told, because a demo never queues anything for it to drain), and those run with `CLEAROTRON_NO_ENV_FILE=1`, so a stray `.env` can neither put a live install into demo mode nor take a demo out of one. Anything but the literal `1` is not a demo. Replaces `PORTAL_DEMO`, which named only one of the processes that has to know. |
|
|
286
|
+
| `CLEAROTRON_TEST_FIXTURE_PROFILES` | unset | `1` makes the profile loader return the three suite fixtures, which are refused from every roster otherwise. Set by `scripts/test-run.mjs`, never by an operator; an explicit `includeTestFixtures` argument beats it. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
287
|
+
| `CLEAROTRON_DEMO_PROFILES` | unset | `1` makes the profile loader return the bundled demo account, which a fresh install does not resolve (owner ruling 2026-09-08). Set by `clearotron demo`, `start --demo` and the suite runner, never by an operator. The gate is on the bundled layer, so a deployment's own configured store keeps its `demoData` accounts either way. Effect class `harness`; the full contract is its row in `.env.example`. |
|
|
286
288
|
| `PORTAL_LOCAL_CREDENTIAL` | `~/.cordillera/portal-local-credential.json` | Where local sign-in keeps its passphrase DIGEST. `npx clearotron demo` points it inside the demo's own base directory, so a demo mints its own passphrase instead of inheriting a digest minted for another address — and removing the demo stays one `rm -rf`. |
|
|
287
289
|
| `PORTAL_LOCAL_PASSPHRASE` | unset | **NEVER set this in a file.** An internal one-shot handoff, not an operator control: on a first FOREGROUND start the supervisor mints the passphrase and hands it to the portal it spawns *at the spawn call*, so the closing summary can print the value beside the address rather than sending a first-time reader back into eleven startup log lines for the one value in this product that cannot be read back. It is deliberately absent from the composed child environments, because that composition is what `--background` writes into the units' env file — a passphrase there would be a permanent plaintext copy on disk and the product's own sentence, "it is stored only as a digest", would stop being true. Setting it in any env file recreates exactly that. Lost passphrase: `clearotron passphrase --reset`. |
|
|
288
290
|
| `PORTAL_URL` | `http://127.0.0.1:18802`, or built from `PORTAL_SERVICE_HOST`/`PORTAL_SERVICE_PORT` | Where the deploy tick's live-surface check expects to reach the portal. |
|
|
@@ -303,6 +303,20 @@ Staff MCP: `TRADEMARK_MCP_HTTP_PORT` (18790), `TRADEMARK_MCP_HTTP_HOST`,
|
|
|
303
303
|
`TRADEMARK_MCP_EMAIL_CLAIM`, `TRADEMARK_MCP_AUTH_HEADER`, `TRADEMARK_MCP_URL`,
|
|
304
304
|
`TRADEMARK_MCP_AUTH_MODE` (see below),
|
|
305
305
|
`TRADEMARK_MCP_AUTH_DISABLED` / `TRADEMARK_MCP_DEV` (dev seams — never set in prod).
|
|
306
|
+
|
|
307
|
+
`TRADEMARK_MCP_KEY_SOCKET` is the local key door, and it is a PATH rather than a port on purpose. This
|
|
308
|
+
interface serves two populations that authenticate differently: people arriving through a tunnel, who
|
|
309
|
+
prove themselves with a proxy identity, and programs on the same machine holding a scoped access key,
|
|
310
|
+
which can never produce one. "Only accept a key from loopback" does not separate them — the tunnel daemon
|
|
311
|
+
runs on the same machine, so a request forwarded from the internet and one from the local portal arrive
|
|
312
|
+
with the same peer address. A tunnel forwards to a port and cannot reach a unix socket, so the transport
|
|
313
|
+
is the discriminator and the two doors are separate objects with opposite rules.
|
|
314
|
+
|
|
315
|
+
Unset means no key door, and a deployment gets exactly the door it had before. The socket is created
|
|
316
|
+
`0660` — owner and group only — with the mode set after `listen`, because `listen` creates the file and
|
|
317
|
+
the umask decides what it starts as. It refuses to open at all with no grants file, alongside
|
|
318
|
+
`TRADEMARK_MCP_AUTH_DISABLED`, or when `TRADEMARK_MCP_AUTH_MODE` is `token`: that mode makes the NETWORK
|
|
319
|
+
door take a key too, which is the thing this exists to prevent. Read by `mcp-server/key-socket.mjs`.
|
|
306
320
|
Client MCP: `CLIENT_MCP_HTTP_PORT` (code default 18811, matching the deployed unit; it was 18795,
|
|
307
321
|
which collided with a co-hosted warm-MCP block, so keep any unit override that names a port),
|
|
308
322
|
`CLIENT_MCP_HTTP_HOST`, `CLIENT_MCP_ALLOWED_HOSTS`,
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
> Part of the architecture pack (`docs/architecture/`). The driver's module tree and the headless
|
|
4
4
|
> integrator contract are in [`driver/README.md`](../../driver/README.md).
|
|
5
5
|
> This chapter describes the profile *mechanism*. Real customer bundles load from an external store
|
|
6
|
-
> via `CLEAROTRON_CUSTOMERS_DIR` and no customer is named here; the
|
|
7
|
-
> synthetic
|
|
6
|
+
> via `CLEAROTRON_CUSTOMERS_DIR` and no customer is named here; the package ships `generic` and the demo
|
|
7
|
+
> brand owner, and the repository holds three further synthetic profiles for the test suite — see
|
|
8
8
|
> [`driver/profiles/`](../../driver/profiles/).
|
|
9
9
|
|
|
10
10
|
One engine, never forked — three layers. The reasoning core is shared by every client; the client
|
package/docs/configuration.md
CHANGED
|
@@ -115,9 +115,10 @@ under that framework. Absent, the generic set applies.
|
|
|
115
115
|
|
|
116
116
|
## 3. Client profiles
|
|
117
117
|
|
|
118
|
-
One JSON file per client under [`driver/profiles/`](../driver/profiles/).
|
|
119
|
-
examples: `generic` (the Generic default)
|
|
120
|
-
|
|
118
|
+
One JSON file per client under [`driver/profiles/`](../driver/profiles/). Two are published as working
|
|
119
|
+
examples: `generic` (the Generic default) and the demo brand owner. Three further synthetic profiles —
|
|
120
|
+
gaming, functional drinks and animal health — exist in the repository for the test suite and are left
|
|
121
|
+
out of the package.
|
|
121
122
|
|
|
122
123
|
A job picks its profile by **forwarding domain** — `matchDomains[]`, exact host or dot-suffix. The
|
|
123
124
|
applicant named in a request never selects a profile. Resolution happens once at run start and is
|
package/driver/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,61 @@
|
|
|
1
1
|
# clearotron-driver
|
|
2
2
|
|
|
3
|
+
## 0.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 54ea03a: Fixed: The configuration check no longer reports a problem when the client connector's address is reached with its own token. Putting a single sign-on front before it is the client's choice, and running it token-based is supported. The check still says the sign-in audience was not compared against that address, so it never implies the two agree.
|
|
8
|
+
- 4b6be02: Fixed: A bundled risk framework now states where it came from in words a customer can read. The note used to carry a confidentiality marking, a filename for a document not included, an internal reference number and revision history. It says whose framework it is, who stands behind it and which revision, and nothing else.
|
|
9
|
+
- 63f916c: Fixed: A fresh install's brand-owner list now offers Generic and, with the demo, the demo account. It offered three of our test accounts as well, on the install route that clones the repository.
|
|
10
|
+
- 5d12baa: Fixed: A run record now says which version of the engine's command-line tool served it. A change in results can be traced to a tool upgrade rather than guessed at.
|
|
11
|
+
|
|
12
|
+
Fixed: A version the tool could not report is recorded as unreadable. An absent field could not be told apart from a probe that never ran.
|
|
13
|
+
- 888de5d: Fixed: The deployment check now says whether each scheduled job's timer is still armed. A timer-driven service reads "inactive" between runs and when its timer has been stopped. So a check that asked only about the service could report nothing wrong while the scheduled work had quietly stopped happening.
|
|
14
|
+
- 3a75e22: Fixed: A supplementary memo now succeeds on its first attempt. It cost two turns instead of one, and left a retried-stage mark on a report that had been delivered cleanly.
|
|
15
|
+
|
|
16
|
+
Fixed: A supplementary memo states the rating framework it was reasoned under. A report assessed under a customer's own framework said so; a memo written from it did not.
|
|
17
|
+
- fe22384: Fixed: Installing on a Node version the engine cannot run on now stops at once. It names the version you have, the version needed, and the command that fixes it. Before, the install finished and the first US register search failed with an error that never mentioned Node. The supported floor is Node 22.13 or newer.
|
|
18
|
+
- 74b2cdd: Fixed: Setup now asks which address signs in, instead of turning it into an access rule covering everyone who shares its email domain.
|
|
19
|
+
|
|
20
|
+
For operators: The People & access page now names the setting behind a staff rule, and the file to edit to undo it.
|
|
21
|
+
- 9053919: Fixed: Five things a first-time reader could not act on.
|
|
22
|
+
|
|
23
|
+
A screen that needs a brand owner chosen no longer points at the top left when the menu is collapsed and there is nothing there.
|
|
24
|
+
|
|
25
|
+
The profile editor's Save button now states the one thing that is blocking it. That reason was always there, but it looked the same as the states where nothing is wrong.
|
|
26
|
+
|
|
27
|
+
The default jurisdictions field now says what it does with what you type. Entries are checked against the list and kept, never refused. A region counts as one entry.
|
|
28
|
+
|
|
29
|
+
The trademark categories field explains what those numbers are, with examples, before naming the standard they come from.
|
|
30
|
+
|
|
31
|
+
The row of coloured ratings under "Risk framework in force" now says it belongs to that framework. It used to read as a list of several frameworks. Both editors say it the same way.
|
|
32
|
+
- fe22384: For operators: Clearotron runs on Node 22.13 or newer again, down from 22.19, because its HTTP client moves back to version 7. Anyone who upgraded Node for the last release has nothing to undo.
|
|
33
|
+
- bb899a7: New: A screening report now leads with the read. Each conflict shows its name, band, source and a one-sentence verdict. The paragraph arguing that verdict is one click away. Register filings appear as conflicts only where the reviewer rated them above the lowest band; the rest stay in the filings table.
|
|
34
|
+
|
|
35
|
+
New: What was asked is now at the top of a screening report, with any question about whether it was the right thing to ask. Both used to sit at the bottom.
|
|
36
|
+
|
|
37
|
+
New: The long per-name assessment now opens from the read. The engine has always written it and the page never showed it.
|
|
38
|
+
|
|
39
|
+
New: Register counts say what they counted in their column headers. Territories are named rather than printed as two-letter codes.
|
|
40
|
+
|
|
41
|
+
New: The lines a reader meets before opening anything are written in plain language. That is the summary, the basis line, each conflict's one sentence and the reviewer's notes. Legal vocabulary stays where the detail is.
|
|
42
|
+
|
|
43
|
+
Fixed: Reviewer notes no longer appear on a screening report exported to PDF. They are for the reviewing lawyer, and a report forwarded to a client used to carry them.
|
|
44
|
+
- 2a812d6: Fixed: The New clearance screen now has a **Start a search** button. The button that ran a search used to say "Review clearance", and people could not tell it was the way to begin.
|
|
45
|
+
|
|
46
|
+
Fixed: When a search cannot start yet, the screen says what is still missing, both on the form and beside the button. It used to grey the button out and give no reason at all.
|
|
47
|
+
|
|
48
|
+
Fixed: **Save as search** now confirms the save beside the button that was pressed, names what was saved, and links to it. It gave no sign at the point of the click.
|
|
49
|
+
|
|
50
|
+
Fixed: The unsaved-changes warning no longer fires on a search you have just saved. It compared the form against a blank one and nothing ever reset it.
|
|
51
|
+
|
|
52
|
+
Fixed: The message shown when no search engine is attached now names the setup command for the way you installed. It used to name the one that only works in a copy of the source.
|
|
53
|
+
- 58af0b7: Fixed: The settings page no longer shows the engine as healthy when the engine program cannot be found.
|
|
54
|
+
|
|
55
|
+
That page reports which engine is configured. The New clearance screen reports whether a search can start right now. When those two readings disagreed, neither screen said so.
|
|
56
|
+
|
|
57
|
+
An install could therefore show a green engine while no search would start, and nothing explained the gap. The settings page now names the disagreement and says what to do about it. Running `clearotron doctor` reports the same thing in the same words.
|
|
58
|
+
|
|
3
59
|
## 0.2.2
|
|
4
60
|
|
|
5
61
|
### Patch Changes
|
|
@@ -154,7 +154,7 @@ export function countMatrixCells(findingsContent) {
|
|
|
154
154
|
* "丝绸与铁 / 席尔克": each alternate is its own search term with its own grid — the copper-conduit worker
|
|
155
155
|
* keyed the split forms with full receipts while the validator demanded the compound key, 2026-06-12);
|
|
156
156
|
* or WORD-BOUNDARY substring coverage (a fully-receipted variant covers a LONGER variant containing it
|
|
157
|
-
* as a whole-word phrase — "
|
|
157
|
+
* as a whole-word phrase — "Foxglade Legends II: Oak & Ember" ⊃ "Oak & Ember"; a marketplace phrase
|
|
158
158
|
* search for the short form surfaces every listing of the long form. Mid-word containment does NOT
|
|
159
159
|
* count: "venzyy" ⊅ "venzy" — storefront search tokenizes, a doubled-letter variant is its own term).
|
|
160
160
|
*/
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// ── THE OUTCOME THIS EXISTS TO STOP ─────────────────────────────────────────────────────────────────
|
|
6
6
|
//
|
|
7
7
|
// After the F41 repair the owner restarted the worker, as instructed, and refreshed the portal. It
|
|
8
|
-
// showed the BUNDLED DEMO ROSTER
|
|
8
|
+
// showed the BUNDLED DEMO ROSTER and not his own account.
|
|
9
9
|
//
|
|
10
10
|
// ~/.env repaired 10:51:16
|
|
11
11
|
// worker 10:51:21 ← new config
|
package/driver/dev-portal.mjs
CHANGED
|
@@ -301,7 +301,7 @@ async function loadOutbox(){const r=await(await fetch("/dev/outbox")).json();
|
|
|
301
301
|
let REG=null;
|
|
302
302
|
const esc=(s)=>String(s??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");
|
|
303
303
|
async function loadSearches(){
|
|
304
|
-
const cust=$("#s_cust").value.trim()||"
|
|
304
|
+
const cust=$("#s_cust").value.trim()||"demo-brand-owner";
|
|
305
305
|
try{
|
|
306
306
|
const all=await(await fetch("/recipes")).json();
|
|
307
307
|
if(all.error){$("#searches").innerHTML='<span class="err">'+esc(all.error)+'</span>';return;}
|
|
@@ -320,7 +320,7 @@ async function loadSearches(){
|
|
|
320
320
|
function useProduct(k){const f=$("#f");f.product.value=k;$("#f_recipe").value="";f.mark.focus();}
|
|
321
321
|
function useRecipe(cust,slug){const f=$("#f");f.product.value="";$("#f_recipe").value=slug;f.profile.value=cust;f.mark.focus();}
|
|
322
322
|
async function composeSubmit(save){
|
|
323
|
-
const cust=$("#s_cust").value.trim()||"
|
|
323
|
+
const cust=$("#s_cust").value.trim()||"demo-brand-owner";
|
|
324
324
|
const fd=new FormData($("#sf"));
|
|
325
325
|
const recipe={label:(fd.get("label")||"").trim(),base:fd.get("base")};
|
|
326
326
|
if(fd.get("registerProbe")!=null)recipe.components={registerProbe:true};
|
package/driver/driver.config.mjs
CHANGED
|
@@ -248,7 +248,7 @@ export const config = {
|
|
|
248
248
|
// 2026-07-19: these were joined against DRIVER_DIR, so the driver read framework manifests out of
|
|
249
249
|
// its BUNDLED driver/skills while CLEAROTRON_INSTRUCTIONS_DIR pointed the agent at the config store. A
|
|
250
250
|
// customer whose framework lives only in the config store therefore hard-failed at attachFramework
|
|
251
|
-
// with `framework_manifest_missing` —
|
|
251
|
+
// with `framework_manifest_missing` — a test account's first run died there before any stage ran.
|
|
252
252
|
get skillsRoot() { return dirname(this.skillsDir); },
|
|
253
253
|
|
|
254
254
|
// ── Per-agent paths ───────────────────────────────────────────────────────
|