clearotron 0.3.2-beta.2 → 0.3.2-beta.4
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 +10 -0
- package/INSTALL.md +2 -0
- package/README.md +15 -10
- package/bin/onboard.mjs +4 -4
- package/build-info.json +2 -2
- package/docs/CLIENT-MCP.md +54 -54
- package/docs/DELIVERY.md +16 -15
- package/docs/E2E.md +8 -8
- package/docs/GLOSSARY.md +2 -2
- package/docs/INTAKE.md +11 -11
- package/docs/ONBOARDING.md +17 -16
- package/docs/PORTAL.md +18 -16
- package/docs/README.md +10 -10
- package/docs/SECURITY.md +20 -20
- package/docs/architecture/01-product-overview.md +17 -16
- package/docs/architecture/02-architecture.md +6 -6
- package/docs/architecture/03-run-lifecycle.md +7 -7
- package/docs/architecture/04-configuration-reference.md +14 -13
- package/docs/architecture/05-config-governance.md +30 -29
- package/docs/architecture/05-customer-profiles.md +35 -35
- package/docs/architecture/06-operations-runbook.md +20 -20
- package/docs/architecture/07-quality-and-audit.md +17 -17
- package/docs/architecture/08-development-guide.md +3 -3
- package/docs/architecture/09-security-and-data.md +27 -27
- package/docs/architecture/README.md +1 -1
- package/docs/branding.md +8 -3
- package/docs/configuration.md +18 -18
- package/docs/writing-standard.md +3 -3
- package/driver/CHANGELOG.md +36 -0
- package/driver/package.json +1 -1
- package/driver/portal-service.mjs +34 -4
- package/driver/suite-census.json +114 -48
- package/mcp-server/CHANGELOG.md +8 -0
- package/mcp-server/package.json +1 -1
- package/mcp-server/packs/client/CONNECT.md +6 -6
- package/package.json +1 -1
- package/portal-ui/dist/assets/{index-BsbasHjM.js → index-5UyqAyNM.js} +3384 -2973
- package/portal-ui/dist/assets/{index-DNQpLYZF.css → index-CVOIvdhc.css} +992 -205
- package/portal-ui/dist/index.html +2 -2
- package/portal-ui/package.json +1 -1
- package/providers/_shared/script-form.mjs +2 -2
- package/providers/clarivate/src/core.js +18 -27
- package/providers/oauth-mcp-bridge/CHANGELOG.md +8 -0
- package/providers/oauth-mcp-bridge/package.json +1 -1
- package/providers/signa/src/core.js +3 -3
- package/scripts/ask-ai-render-check.mjs +287 -105
- package/scripts/env-classify.mjs +6 -0
- package/scripts/release-note-required.mjs +16 -2
- package/scripts/settings-render-check.mjs +575 -0
- package/shared/brand.mjs +29 -0
- package/shared/connect-clients.mjs +99 -47
- package/shared/names-in-force.mjs +1 -0
- package/shared/stdio-connect.mjs +16 -2
- package/shared/writing-standard-classes.mjs +34 -3
package/.env.example
CHANGED
|
@@ -603,3 +603,13 @@ CLEAROTRON_DEMO_PROFILES=
|
|
|
603
603
|
# it. Read by bin/start.mjs and shared/brand.mjs.
|
|
604
604
|
# effect: deployment
|
|
605
605
|
CLEAROTRON_ORGANISATION_NAME=
|
|
606
|
+
|
|
607
|
+
# ── Who a person asks to change their sign-in ─────────────────────────────────────────────────────
|
|
608
|
+
#
|
|
609
|
+
# The administrator contact: a mail address (`it@example.com`, or `mailto:it@example.com`) or a web
|
|
610
|
+
# address (`https://help.example.com/access`). Preferences tells a signed-in person to contact their
|
|
611
|
+
# Clearotron administrator to change the address, the permissions or the companies on their sign-in;
|
|
612
|
+
# set, those words are a link to this; unset, they are plain text. Any other value is treated as unset,
|
|
613
|
+
# so nothing but a mail or web address ever becomes a link. Read by shared/brand.mjs.
|
|
614
|
+
# effect: deployment
|
|
615
|
+
CLEAROTRON_ADMINISTRATOR_CONTACT=
|
package/INSTALL.md
CHANGED
|
@@ -438,6 +438,8 @@ CLEAROTRON_CUSTOMERS_DIR=/etc/trademark/profiles # your private customer-config
|
|
|
438
438
|
CLEAROTRON_BRAND_NAME=Your Firm # stamped into report titles, the pool index and Excel metadata
|
|
439
439
|
CLEAROTRON_BRAND_TAGLINE= # empty means ABSENT: no strapline is rendered at all
|
|
440
440
|
CLEAROTRON_BRAND_PRODUCT=Trademark clearance # what the deliverable is called
|
|
441
|
+
# Optional: a mail or web address. Preferences links "Clearotron administrator" to it; unset, plain words.
|
|
442
|
+
# CLEAROTRON_ADMINISTRATOR_CONTACT=it@your-firm.example
|
|
441
443
|
|
|
442
444
|
# ── Register provider (choose ONE) ─────────────────────────────────────
|
|
443
445
|
CLEAROTRON_DATABASE=clarivate # REQUIRED — corsearch | clarivate | signa | euipo | uspto-local | free-tier
|
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
<a href=".nvmrc"><img src="https://img.shields.io/badge/node-%E2%89%A5%2022.13-250902?style=flat-square" alt="Node 22.13+"></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
Before your company commits to a name, find out what stands in its way. Give Clearotron the name, the
|
|
14
|
+
classes you trade in and the territories you sell into: it searches the trademark registers and the
|
|
15
|
+
open web for conflicts, reasons about the risk the way a clearance lawyer would, and publishes a
|
|
16
|
+
written report with a machine-readable audit trail behind every finding. It runs headless on your own
|
|
17
|
+
machine: no gateway, no platform, and nothing about the names you are clearing reaches us.
|
|
17
18
|
|
|
18
19
|
[Quickstart](QUICKSTART.md) · [Install & operate](INSTALL.md) · [Docs](docs/README.md) · [Security](docs/SECURITY.md) · [Contributing](CONTRIBUTING.md) · [Licence](#licence)
|
|
19
20
|
|
|
@@ -27,7 +28,7 @@ npx clearotron demo
|
|
|
27
28
|
|
|
28
29
|
That fetches the published package — it will ask once before downloading — then replays finished
|
|
29
30
|
clearances into a local portal and prints the portal's address and the passphrase to sign in with. Open
|
|
30
|
-
the address in your browser. No
|
|
31
|
+
the address in your browser. No sign-up, no credentials, no network calls to us.
|
|
31
32
|
|
|
32
33
|
The demo runs for as long as that window stays open, and removes everything it made when you close it —
|
|
33
34
|
nothing of it is left on the machine, and running it again later starts clean. If you want to keep the
|
|
@@ -82,8 +83,8 @@ Then start the product and open the portal address it prints:
|
|
|
82
83
|
clearotron start
|
|
83
84
|
```
|
|
84
85
|
|
|
85
|
-
That is the portal
|
|
86
|
-
sentence, or set the classes, marketplaces and search depth yourself:
|
|
86
|
+
That is the portal everyone at your company uses. Ordering a clearance is the same screen — describe it
|
|
87
|
+
in a sentence, or set the classes, marketplaces and search depth yourself:
|
|
87
88
|
|
|
88
89
|

|
|
89
90
|
|
|
@@ -115,12 +116,16 @@ clearotron run --job my-job.json
|
|
|
115
116
|
**HTTP** face whose read tools serve a signed-in identity while its write verbs — `start_run`
|
|
116
117
|
among them, which spends — need an ops token. [Connect it](mcp-server/CONNECT.md).
|
|
117
118
|
- **The engine is not coupled to a vendor.** [`driver/register-plan.mjs`](driver/register-plan.mjs) — which decides what gets searched — takes a capabilities object as a parameter and imports no provider at all. An unknown register id throws rather than falling back.
|
|
119
|
+
- **A law firm runs one installation for every company it acts for.** Each company is set up once, with
|
|
120
|
+
its own classes, marketplaces and risk framework, and each person sees only the companies they are
|
|
121
|
+
given. [Adding a company](docs/ONBOARDING.md) · [A connector for those companies' people](docs/CLIENT-MCP.md).
|
|
118
122
|
|
|
119
123
|
## Security
|
|
120
124
|
|
|
121
|
-
Reports
|
|
125
|
+
Reports describe names your company has not announced yet. Treat the pool, the archive and the delivery
|
|
126
|
+
packets as you would any unreleased plan — and, at a law firm, as you would a case file.
|
|
122
127
|
|
|
123
|
-
**The authors of this software receive nothing** — no marks, no
|
|
128
|
+
**The authors of this software receive nothing** — no marks, no company context, no results, no usage
|
|
124
129
|
reports, no crash reports. There is no telemetry in this tree and no endpoint we control: every
|
|
125
130
|
destination is a register, a reasoning provider or a search provider you configured with your own
|
|
126
131
|
credential.
|
|
@@ -139,7 +144,7 @@ credential.
|
|
|
139
144
|
| Check it works before spending anything | [docs/E2E.md](docs/E2E.md) |
|
|
140
145
|
| Understand the architecture | [docs/architecture/](docs/architecture/) · [decisions](docs/decisions/) |
|
|
141
146
|
| Run it under your own name, or fork it | [docs/branding.md](docs/branding.md) · [TRADEMARKS.md](TRADEMARKS.md) |
|
|
142
|
-
| Write a sentence a
|
|
147
|
+
| Write a sentence a user will read | [docs/writing-standard.md](docs/writing-standard.md) · [docs/writing-rules.md](docs/writing-rules.md) |
|
|
143
148
|
|
|
144
149
|
## Development
|
|
145
150
|
|
package/bin/onboard.mjs
CHANGED
|
@@ -2015,7 +2015,7 @@ export async function runCheck() {
|
|
|
2015
2015
|
if (!prov) {
|
|
2016
2016
|
blocking(`no register is selected — CLEAROTRON_DATABASE is not set and there is NO default, so every search refuses until one is`);
|
|
2017
2017
|
info(` set it to one of: ${PROVIDERS.map((p) => p.id).join(", ")} — any one of them is enough, and none needs another`);
|
|
2018
|
-
info(` re-run \`${invoke("install")}\`, or set it on the
|
|
2018
|
+
info(` re-run \`${invoke("install")}\`, or set it on the Installation settings page`);
|
|
2019
2019
|
}
|
|
2020
2020
|
else {
|
|
2021
2021
|
const spec = PROVIDERS.find((p) => p.id === prov.v);
|
|
@@ -3565,7 +3565,7 @@ try {
|
|
|
3565
3565
|
//
|
|
3566
3566
|
// NOTHING DOWNSTREAM NEEDED CHANGING, and that is the owner's point rather than luck: CLEAROTRON_DATABASE
|
|
3567
3567
|
// is single-valued with no default, a run already refuses by name when it is unset
|
|
3568
|
-
// (driver.config.mjs), and the
|
|
3568
|
+
// (driver.config.mjs), and the Installation settings page already says a register is needed.
|
|
3569
3569
|
// One register per install, any one of them sufficient, none a precondition for another.
|
|
3570
3570
|
let registerSelected = true;
|
|
3571
3571
|
// What THIS step collected, so abandoning the selection can take it back. Measured: a register with
|
|
@@ -3591,7 +3591,7 @@ try {
|
|
|
3591
3591
|
say("");
|
|
3592
3592
|
info("No register is selected, and nothing register-related will be written.");
|
|
3593
3593
|
info(`Every search refuses until one is set — \`${invoke("doctor")}\` says so on every run, and the`);
|
|
3594
|
-
info("
|
|
3594
|
+
info(" Installation settings page says it too. Re-run setup, or set it there, when you have a credential.");
|
|
3595
3595
|
}
|
|
3596
3596
|
for (const k of registerSelected ? (spec.optionalCredentials ?? []) : []) {
|
|
3597
3597
|
if (present(candidate[k])) { ok(`${k} already adopted from your environment`); continue; }
|
|
@@ -4142,7 +4142,7 @@ try {
|
|
|
4142
4142
|
say(` \`${invocationPrefix()}clearotron demo\` and \`${invocationPrefix()}clearotron start\` work now. A real`);
|
|
4143
4143
|
say(" clearance needs one register — any one is enough, and none requires another:");
|
|
4144
4144
|
say(` ${PROVIDERS.map((p) => p.id).join(", ")}`);
|
|
4145
|
-
say(` Set it by re-running \`${invocationPrefix()}clearotron install\`, or on the
|
|
4145
|
+
say(` Set it by re-running \`${invocationPrefix()}clearotron install\`, or on the Installation settings page.`);
|
|
4146
4146
|
say(` \`${invocationPrefix()}clearotron doctor\` says which state this install is in, at any time.\n`);
|
|
4147
4147
|
}
|
|
4148
4148
|
} catch (e) {
|
package/build-info.json
CHANGED
package/docs/CLIENT-MCP.md
CHANGED
|
@@ -5,44 +5,44 @@ definitions.*
|
|
|
5
5
|
|
|
6
6
|
> **Just want to connect your own app to your own runs?** You do not need any of this — spawn the stdio
|
|
7
7
|
> server from your clone. [`mcp-server/CONNECT.md`](../mcp-server/CONNECT.md) is four lines of
|
|
8
|
-
> copy-paste. This document is for publishing a connector
|
|
8
|
+
> copy-paste. This document is for publishing a connector that each *company's* people sign in to.
|
|
9
9
|
|
|
10
|
-
What
|
|
10
|
+
What they reach once you have, what they cannot, and how to turn it on. For the staff/ops MCP
|
|
11
11
|
faces see `docs/architecture/09-security-and-data.md`.
|
|
12
12
|
|
|
13
13
|
## The three faces, in one table
|
|
14
14
|
|
|
15
15
|
One codebase (`mcp-server/server.mjs`), three processes. The separation is per-process **configuration**,
|
|
16
|
-
never a runtime branch — "a
|
|
17
|
-
not about a flag being right.
|
|
16
|
+
never a runtime branch — "a company's person cannot reach staff read-all" is a fact about which binary is
|
|
17
|
+
listening, not about a flag being right.
|
|
18
18
|
|
|
19
19
|
| Face | Where it listens | Who | Reach |
|
|
20
20
|
|---|---|---|---|
|
|
21
|
-
| **Staff** | your staff hostname → `TRADEMARK_MCP_HTTP_PORT` (default 18790) |
|
|
22
|
-
| **Client** | your client hostname → `CLIENT_MCP_HTTP_PORT` (default 18811) |
|
|
21
|
+
| **Staff** | your staff hostname → `TRADEMARK_MCP_HTTP_PORT` (default 18790) | your staff (staff CF Access AUD) | every read tool, all runs |
|
|
22
|
+
| **Client** | your client hostname → `CLIENT_MCP_HTTP_PORT` (default 18811) | the people of each company (client CF Access AUD) | see below |
|
|
23
23
|
| **Ops** | loopback only, on its own port, **no hostname** | the portal's trigger lane | reads + write verbs |
|
|
24
|
-
| **API key** | a hostname with no Access app in front → its own `CLIENT_MCP_HTTP_PORT` | a
|
|
24
|
+
| **API key** | a hostname with no Access app in front → its own `CLIENT_MCP_HTTP_PORT` | a company's agent that cannot sign in | same as Client |
|
|
25
25
|
|
|
26
26
|
Every port above is the code default and each face is a separate process, so on one machine give
|
|
27
|
-
each its own. The ops face is never on the internet. If you are looking for
|
|
28
|
-
is the Client face. The API-key door is that same client face reached with a credential instead of a
|
|
27
|
+
each its own. The ops face is never on the internet. If you are looking for the face a company's people
|
|
28
|
+
use, it is the Client face. The API-key door is that same client face reached with a credential instead of a
|
|
29
29
|
browser login — see "The API-key door" below.
|
|
30
30
|
|
|
31
|
-
## The two
|
|
31
|
+
## The two principals outside your staff
|
|
32
32
|
|
|
33
33
|
**`user` — a run-bound report link.** A read-only token pinned to one run, no enumeration. The scope
|
|
34
34
|
is served as it always was; what changed with the move to a single report document is how anyone
|
|
35
35
|
comes by one. The token is minted into THE report's "Ask your AI" block against the STAFF connector
|
|
36
36
|
(`CLEAROTRON_MCP_URL`, `render.mjs`), and `portal-report.mjs` strips that whole block for every non-staff
|
|
37
|
-
reader at serve time — so **no
|
|
38
|
-
reaches the connector as `account` instead, at an address the portal serves live from
|
|
37
|
+
reader at serve time — so **no surface a non-staff reader sees hands one out any more**. A signed-in
|
|
38
|
+
person reaches the connector as `account` instead, at an address the portal serves live from
|
|
39
39
|
`/portal/api/mcp-access` (`CLEAROTRON_CLIENT_MCP_URL`, no baked credential). A run-bound link for a
|
|
40
40
|
recipient who has no login is now a deliberate act:
|
|
41
41
|
`node mcp-server/mint-token.mjs --scope user --run <runId>`.
|
|
42
42
|
|
|
43
|
-
**`account` — a signed-in
|
|
44
|
-
|
|
45
|
-
Enrolment is therefore the portal's: no second credential to mint, rotate or revoke, and revoking portal
|
|
43
|
+
**`account` — a signed-in person.** A CF-verified identity from outside your staff with **no token**,
|
|
44
|
+
resolved to the companies their email is granted (`CLEAROTRON_ACCESS_FILE` — the same guest list the portal
|
|
45
|
+
uses). Enrolment is therefore the portal's: no second credential to mint, rotate or revoke, and revoking portal
|
|
46
46
|
access revokes this with it. **Off unless `CLIENT_MCP_ACCOUNT_ACCESS=1`.**
|
|
47
47
|
|
|
48
48
|
**Who turns that on. The installer, since 2026-09-03** — ruling, settled
|
|
@@ -53,9 +53,9 @@ come from one authority, `enablePlan` in `shared/client-door.mjs`, which is also
|
|
|
53
53
|
|
|
54
54
|
**This supersedes the 2026-08-31 ruling** *"On demand is fine"*, under which nothing at install and no
|
|
55
55
|
rebuild's enable list could start this unit, because starting it WAS the consent that opened
|
|
56
|
-
|
|
57
|
-
whether a process runs.** A door with no key issued refuses everything, which is the same
|
|
58
|
-
a mechanism that does not depend on a reader finding a verb.
|
|
56
|
+
access for each company's people. The owner changed the posture knowingly: **the per-person key is the
|
|
57
|
+
gate, not whether a process runs.** A door with no key issued refuses everything, which is the same
|
|
58
|
+
protection by a mechanism that does not depend on a reader finding a verb.
|
|
59
59
|
|
|
60
60
|
**`npx clearotron disconnect` therefore revokes a person, not a service** (Q3). It writes the caller's key
|
|
61
61
|
ids to the denylist and strikes them from the record; it does not stop the unit and does not touch
|
|
@@ -63,58 +63,58 @@ ids to the denylist and strikes them from the record; it does not stop the unit
|
|
|
63
63
|
`npx clearotron disconnect --everyone`, which states how many keys and how many people that is before
|
|
64
64
|
acting — and does not stop the service either.
|
|
65
65
|
|
|
66
|
-
An `account` principal reaches **eighteen** tools, for its own
|
|
66
|
+
An `account` principal reaches **eighteen** tools, for its own companies only — everything carrying
|
|
67
67
|
`accountSafe: true` in `TOOL_SCOPES` (`shared/scope.mjs`), and nothing else:
|
|
68
68
|
|
|
69
69
|
| Layer | Tools |
|
|
70
70
|
|---|---|
|
|
71
|
-
| the report | `brief`, `read_artifact` (the report; `clientSummary` is retired from
|
|
71
|
+
| the report | `brief`, `read_artifact` (the report; `clientSummary` is retired from this face and stays an ops-only internal source), `list_findings` (curated cards) |
|
|
72
72
|
| the evidence behind it | `list_evidence`, `list_searches`, `get_search_coverage` |
|
|
73
73
|
| the audit chain | `read_artifact` over `audit`, `narrative`, `registerFindings`, `commonLaw`, `caseLaw`, `matterContext` and `registerUnit:<axis>`; `list_findings` on the raw `kind` path; `get_finding`, `get_run`, `trace`, `decision_timeline` |
|
|
74
74
|
| the run lifecycle | `list_runs`, `describe_options`, `plan_run`, `start_run`, `stop_run` |
|
|
75
75
|
| what-if | `what_if_plan` (free), `what_if_run` (queues a sandbox job), `what_if_result` (collects it) |
|
|
76
76
|
|
|
77
|
-
The evidence layer exists because a
|
|
77
|
+
The evidence layer exists because a lawyer defending a filing decision needs the records
|
|
78
78
|
under the report, not just its prose. It projects named structured fields and enums derived from
|
|
79
79
|
them — `mcp-server/lib/evidence.mjs` states that there is no code path forwarding free prose, and
|
|
80
80
|
that is the one declared exception to the scrub.
|
|
81
81
|
|
|
82
|
-
**The audit chain is open by ruling, 2026-08-27
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
**The audit chain is open by ruling, 2026-08-27**, with the call spend set aside. The same lawyer who
|
|
83
|
+
needs the records also has to be able to show *how* the answer was reached, so the decision chain is
|
|
84
|
+
part of what this face serves now. Unlike the
|
|
85
85
|
evidence layer this one does forward prose — a chain of reasoning is prose — so it is bounded a
|
|
86
86
|
different way: `mcp-server/lib/audit-view.mjs` names the structural fields that travel and puts the
|
|
87
|
-
surviving prose through the report's own
|
|
87
|
+
surviving prose through the report's own scrub passes, never a second copy of them.
|
|
88
88
|
|
|
89
89
|
Three things stayed behind, and each has a reason rather than a habit:
|
|
90
90
|
|
|
91
91
|
- **Cost.** `get_telemetry` and `get_provider_usage` exist to report model identity and billed
|
|
92
92
|
counts. Every other decision-chain read is model-free by construction — `events.mjs`, `trace.mjs`
|
|
93
|
-
and `getStages` each say so on their own surface — so sealing exactly these two costs
|
|
93
|
+
and `getStages` each say so on their own surface — so sealing exactly these two costs the reader
|
|
94
94
|
nothing of the chain.
|
|
95
95
|
- **The engine's judgment of its own output.** `skepticFlags` and `seniorEyeReview` are the reviewers
|
|
96
96
|
writing about our draft, the same class as the `withdrawn_reason` ruling. The verdict they
|
|
97
97
|
produced travels; the critique does not.
|
|
98
98
|
- **The unruled reads.** `get_coverage`, `search`, `search_runs`, `diff_artifact`, `run_changes`,
|
|
99
99
|
`list_profiles`, delivery/outbox, `feed_context` and what-if. Nobody has decided what these should
|
|
100
|
-
show a
|
|
100
|
+
show a company's people, and an undecided tool is denied — `get_search_coverage` is deliberately not
|
|
101
101
|
`get_coverage`, the latter being the engineering artifact-validity view.
|
|
102
102
|
|
|
103
103
|
**What-if is a QUEUED JOB on this surface, and that is what keeps the door honest.** The remote faces
|
|
104
104
|
never spawn the engine — `http-server.mjs` states it as a configuration fact and `lib/whatif.mjs`'s lazy
|
|
105
|
-
import of `driver/pipeline.mjs` is what holds it — so a
|
|
105
|
+
import of `driver/pipeline.mjs` is what holds it — so a `what_if_run` on this face does not execute. It
|
|
106
106
|
validates, enqueues into `<runDir>/_experiments/_queue/`, and returns an `experimentId`;
|
|
107
107
|
`driver/whatif-worker.mjs`, drained by the runner in an OS service process, is what spawns the sandbox.
|
|
108
|
-
The
|
|
108
|
+
The caller collects the diff with `what_if_result`. The original run is never modified — the experiment
|
|
109
109
|
writes only under `_experiments/`.
|
|
110
110
|
|
|
111
111
|
Four things about it are worth knowing before you offer it:
|
|
112
112
|
|
|
113
|
-
- **The confirmation-token handshake stays, and
|
|
114
|
-
base64url JSON that nothing signs, so a token-only call would slip past the
|
|
113
|
+
- **The confirmation-token handshake stays, and the caller must ALSO name the run.** A token is plain
|
|
114
|
+
base64url JSON that nothing signs, so a token-only call would slip past the grant check, which keys on
|
|
115
115
|
`runId`. Naming the run puts the grant check in the path; `whatIfEnqueue` then proves the token names
|
|
116
116
|
the same run, so neither half can be satisfied alone.
|
|
117
|
-
- **
|
|
117
|
+
- **The caller cannot choose the model.** The tier is cost and method both, and it is the one argument on
|
|
118
118
|
the one tool that spends. Express the change with `instructions`.
|
|
119
119
|
- **Nothing bounds the spend, by ruling.** `start_run` is stamped `clientPrincipal: true` at the
|
|
120
120
|
chokepoint so `runCaps.dailyRuns` bites it; a what-if job carries no such stamp, because the owner
|
|
@@ -125,15 +125,15 @@ Four things about it are worth knowing before you offer it:
|
|
|
125
125
|
- **It starts on the timer, not instantly.** The systemd `.path` unit watches the clearance queue dirs,
|
|
126
126
|
not run dirs, so a queued what-if is picked up on the runner's 90s tick.
|
|
127
127
|
|
|
128
|
-
**A forwardable report link did not move.** `USER_ARTIFACTS` gated `read_artifact` for both
|
|
129
|
-
kinds, so the account layer was given its own set (`ACCOUNT_ARTIFACTS`) rather than the shared one
|
|
128
|
+
**A forwardable report link did not move.** `USER_ARTIFACTS` gated `read_artifact` for both principal
|
|
129
|
+
kinds, so the `account` layer was given its own set (`ACCOUNT_ARTIFACTS`) rather than the shared one
|
|
130
130
|
being widened: a run-bound `user` token rides inside a delivered PDF and can be forwarded to anyone,
|
|
131
|
-
and the ruling was about
|
|
131
|
+
and the ruling was about people the operator enrolled.
|
|
132
132
|
|
|
133
|
-
## What a
|
|
133
|
+
## What a company's people see of a report
|
|
134
134
|
|
|
135
|
-
The
|
|
136
|
-
`report.html` as served to a
|
|
135
|
+
The cut, which is **what THE report already shows them — no more, and no less**:
|
|
136
|
+
`report.html` as served to a non-staff reader through the portal's `readReport()`
|
|
137
137
|
preparation (`mcp-server/lib/scrub.mjs` states the rule; `driver/portal-report.mjs` is the serve-time
|
|
138
138
|
counterpart).
|
|
139
139
|
|
|
@@ -142,23 +142,23 @@ hash, and the `tier`/`label` card shorthand the report footer says is "removed o
|
|
|
142
142
|
|
|
143
143
|
**Kept, deliberately:** the Methodology section and the register/common-law provider names. Both are in the
|
|
144
144
|
delivered report already (`render.mjs` renders Methodology via `plainScopeNote`; provider names are named
|
|
145
|
-
for provenance honesty). A scrubber stricter than the report would delete content the
|
|
145
|
+
for provenance honesty). A scrubber stricter than the report would delete content the reader was already
|
|
146
146
|
sent and make the connector a different product from the PDF in their inbox. **If you want less exposed,
|
|
147
147
|
change the report render or the serve-time preparation (`portal-report.mjs`) — it flows here for free.
|
|
148
148
|
Never add an MCP-only rule.**
|
|
149
149
|
|
|
150
|
-
## The daily allowance — read this before enabling a demo
|
|
150
|
+
## The daily allowance — read this before enabling a demo company
|
|
151
151
|
|
|
152
|
-
|
|
152
|
+
Someone starting a run over this face spends real money. The control is `runCaps.dailyRuns` on the company
|
|
153
153
|
profile, enforced at the runner's admission gate (so it covers every door) plus a portal pre-check.
|
|
154
154
|
|
|
155
155
|
It only bites jobs stamped `clientPrincipal: true`, and **that stamp is positive-only — absence means
|
|
156
|
-
uncapped**. `authorize()` forces it for an `account` principal, so a
|
|
157
|
-
Staff runs deliberately never consume a
|
|
156
|
+
uncapped**. `authorize()` forces it for an `account` principal, so a caller cannot omit it or pass `false`.
|
|
157
|
+
Staff runs deliberately never consume a company's allowance.
|
|
158
158
|
|
|
159
|
-
**For a demo or pitch
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
**For a demo or pitch company, set `dailyRuns` low (1–2) and `maxQueued: 1`.** Without `dailyRuns` the
|
|
160
|
+
company is uncapped by day and can exhaust the weekly engine capacity in a sitting. Prefer a synthetic
|
|
161
|
+
company for demos so the data is disposable too.
|
|
162
162
|
|
|
163
163
|
## The API-key door — for agents that cannot sign in
|
|
164
164
|
|
|
@@ -173,12 +173,12 @@ node mcp-server/mint-token.mjs --scope account --sub lawyer@acme.example [--acco
|
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
**The key proves WHO; the grants file still decides WHAT.** `--sub` names an identity that must appear in
|
|
176
|
-
`CLEAROTRON_ACCESS_FILE`, and the
|
|
176
|
+
`CLEAROTRON_ACCESS_FILE`, and the companies are resolved from that file **on every request** — never baked into
|
|
177
177
|
the token. `--accounts` is a CAP (an intersection on top of the grant) and can only narrow it. So there are
|
|
178
178
|
two independent revocation levers: **delete the grants row** (instant, needs no re-minting) or **denylist
|
|
179
179
|
the `jti`** (`TRADEMARK_MCP_TOKEN_DENYLIST`).
|
|
180
180
|
|
|
181
|
-
It resolves to the **same `account` principal** as a signed-in
|
|
181
|
+
It resolves to the **same `account` principal** as a signed-in person, so the tool set, `authorize()`, the
|
|
182
182
|
scrub and `runCaps` above all apply unchanged — there is no second policy to keep in step.
|
|
183
183
|
|
|
184
184
|
**It is a separate process, not a flag on the client door** (`CLIENT_MCP_TOKEN_ONLY=1`, its own
|
|
@@ -196,7 +196,7 @@ A key may be presented as `Authorization: Bearer <key>`, a bare `Authorization:
|
|
|
196
196
|
say in which, and `?token=` is the fallback that needs no header at all. On the CF-fronted doors
|
|
197
197
|
`Authorization` is deliberately **not** read as a trademark key: there it belongs to the proxy/agent.
|
|
198
198
|
|
|
199
|
-
**Give a key a real
|
|
199
|
+
**Give a key a real company with real `runCaps`, never `generic`** — `generic` is cap-exempt, so a
|
|
200
200
|
long-lived credential pointed at it can spend without limit.
|
|
201
201
|
|
|
202
202
|
### Standing it up
|
|
@@ -209,7 +209,7 @@ long-lived credential pointed at it can spend without limit.
|
|
|
209
209
|
`CLIENT_MCP_ACCOUNT_ACCESS=1` + `CLEAROTRON_ACCESS_FILE`, and drop the CF AUD lines (unused here).
|
|
210
210
|
3. Verify before exposing: `curl 127.0.0.1:<port>/healthz`; the boot log says `API-KEY door — no auth
|
|
211
211
|
proxy in front`; an MCP `initialize` **without** a key is a 401.
|
|
212
|
-
4. Mint a key, hand it over with the address. `tools/list` must be exactly the eleven account tools
|
|
212
|
+
4. Mint a key, hand it over with the address. `tools/list` must be exactly the eleven `account` tools
|
|
213
213
|
above — anything more means the door resolved a wider principal than `account`.
|
|
214
214
|
|
|
215
215
|
## Turning it on
|
|
@@ -227,19 +227,19 @@ template, and a placeholder that looks configured is worse than unset.**
|
|
|
227
227
|
and `client ACCOUNT access ON`. Starting with the flag set and no grants file is a **FATAL**
|
|
228
228
|
refusal, by design.
|
|
229
229
|
4. At your edge: confirm the client hostname resolves to that port and that the **client** Access app
|
|
230
|
-
fronts it — not the staff one. Enrol the
|
|
230
|
+
fronts it — not the staff one. Enrol the addresses of each company's people on that app's policy.
|
|
231
231
|
5. `CLEAROTRON_CLIENT_MCP_URL` must be set for the portal process too, or `/portal/api/mcp-access` answers
|
|
232
232
|
`{url:null}` and the Use-your-AI screen correctly shows its empty state.
|
|
233
233
|
|
|
234
234
|
The client connector address is served **live**: `/portal/api/mcp-access` reads `CLEAROTRON_CLIENT_MCP_URL`
|
|
235
|
-
at request time, so a change reaches every
|
|
235
|
+
at request time, so a change reaches every screen that shows it on the next load — no re-render involved. The
|
|
236
236
|
render no longer reads this variable at all: the block baked into
|
|
237
237
|
`report.html` is the STAFF connector (`CLEAROTRON_MCP_URL`), and it is stripped for non-staff readers at
|
|
238
238
|
serve time.
|
|
239
239
|
|
|
240
|
-
## What briefs
|
|
240
|
+
## What briefs a company's assistant
|
|
241
241
|
|
|
242
|
-
`skills/clearotron-client/SKILL.md`, served as the MCP `instructions` field on initialize — clients surface
|
|
242
|
+
`skills/clearotron-client/SKILL.md`, served as the MCP `instructions` field on initialize — MCP clients surface
|
|
243
243
|
it to their model on connect. It sets voice (plain language, no codes), the tool ladder, the verdict
|
|
244
244
|
vocabulary, evidence drill-through, and the three "never"s.
|
|
245
245
|
|
package/docs/DELIVERY.md
CHANGED
|
@@ -152,9 +152,9 @@ cap. An integrator that only runs during the day leaves a failure unreported unt
|
|
|
152
152
|
nothing in the product can compensate for that: the product composes the notice and records that it is
|
|
153
153
|
owed, and sending is yours.
|
|
154
154
|
|
|
155
|
-
**Two things that make that list incomplete, both silent.** A token scoped to named
|
|
156
|
-
those
|
|
157
|
-
mint the integrator's token to cover every
|
|
155
|
+
**Two things that make that list incomplete, both silent.** A token scoped to named companies sees only
|
|
156
|
+
those companies' runs, so a run for a company the token does not carry is invisible rather than absent —
|
|
157
|
+
mint the integrator's token to cover every company it delivers for, and re-mint it when one is added.
|
|
158
158
|
And a `limit` you pass yourself is obeyed as given: for this query, do not pass one.
|
|
159
159
|
|
|
160
160
|
The filesystem loop below remains equivalent for integrators that do have data-plane access.
|
|
@@ -194,29 +194,29 @@ double-sends (`.sent` guards it).
|
|
|
194
194
|
|
|
195
195
|
## One report per run
|
|
196
196
|
|
|
197
|
-
A run publishes ONE report document per mark, on every lane. There is no internal variant and no
|
|
198
|
-
|
|
197
|
+
A run publishes ONE report document per mark, on every lane. There is no internal variant and no second
|
|
198
|
+
one for outside readers, and nothing writes `report.client.html` — internal working material (staff notes, the
|
|
199
199
|
model's register estimate) is not stripped from the report, it is not in the report: it lives in the
|
|
200
200
|
audit workbook. Two renderings of one run is how the wrong link gets sent.
|
|
201
201
|
|
|
202
202
|
Beside it the run publishes `report-data.json` (`schema: "report-data/1"`): the run as data — level
|
|
203
203
|
identity, bands, per-mark points, evidence links, register counts. That is the input a bespoke,
|
|
204
|
-
forwardable
|
|
205
|
-
pointing at the report; per-
|
|
204
|
+
forwardable email is drafted from. The engine composes exactly one email shape, a cover note
|
|
205
|
+
pointing at the report; per-company formatting is not a config knob.
|
|
206
206
|
|
|
207
207
|
The second document was a real hazard while it existed: any surface that opened a report **by file
|
|
208
|
-
path** bypassed the serve-time preparation, so a
|
|
208
|
+
path** bypassed the serve-time preparation, so a non-staff path pointed at the wrong file served
|
|
209
209
|
the internal report. Two properties close that, and both are load-bearing for anyone building a
|
|
210
|
-
|
|
210
|
+
surface for non-staff readers on this engine:
|
|
211
211
|
|
|
212
|
-
- **Publish writes one file.** No lane produces `report.client.html`, and the per-
|
|
212
|
+
- **Publish writes one file.** No lane produces `report.client.html`, and the per-company index
|
|
213
213
|
(`customer/<key>/index.html`) links `report.html` (`publish/index.mjs`) with no split language.
|
|
214
214
|
- **One preparation chokepoint.** What a non-staff reader receives is prepared by the portal's
|
|
215
215
|
`readReport()` (`driver/portal-report.mjs`, `staff:false`), which removes the reader-visible
|
|
216
216
|
deltas — `[internal]` review tails, the internal band/reviewer shorthand, the staff Ask-your-AI
|
|
217
217
|
connector — in one place, rather than at render time into a second document.
|
|
218
218
|
|
|
219
|
-
**A
|
|
219
|
+
**A surface for non-staff readers must read through `readReport()`, never open a report by path.** That is
|
|
220
220
|
the whole guarantee: the preparation is on the read, so a surface that skips it serves unprepared
|
|
221
221
|
bytes. Pool directories from before the change may still hold a `report.client.html`; nothing reads
|
|
222
222
|
those files, and `publish/pool-admin.mjs` deliberately leaves them alone rather than retrofitting
|
|
@@ -256,9 +256,10 @@ There is no preflight that fails a run before anything touches the pool, and no
|
|
|
256
256
|
`deliveryFlagLines`): one plain sentence per failing check, with a count. The checks' own `detail`
|
|
257
257
|
never leaves the internal lane — it quotes fetch causes, register URIs, model field names and
|
|
258
258
|
instructions to whoever re-runs the job. **The cover note carries no machine-check block**, on
|
|
259
|
-
either lane: `emailBodyHtml` is sent verbatim to `forwarderEmail`, which on a
|
|
260
|
-
the
|
|
261
|
-
reader cannot act on it. One enumeration surface, and it is the one the reviewer
|
|
259
|
+
either lane: `emailBodyHtml` is sent verbatim to `forwarderEmail`, which on a run started by someone outside
|
|
260
|
+
the operator's staff is that person's own address, and a failing internal check does not change what
|
|
261
|
+
was searched, so that reader cannot act on it. One enumeration surface, and it is the one the reviewer
|
|
262
|
+
already opens.
|
|
262
263
|
|
|
263
264
|
### The knockout lane runs a SUBSET of the predelivery lint
|
|
264
265
|
|
|
@@ -275,7 +276,7 @@ the store-rendered end-state: `publishKnockout` renders from validated `knockout
|
|
|
275
276
|
`validateMergedFindings` plus the per-chunk validators are its own lint — schema, ladder vocabulary,
|
|
276
277
|
plan-parity, degraded-parity, tone, quantitative claims, URL receipts. Most clearance checks then
|
|
277
278
|
read surfaces this lane does not produce: no register record store (it counts hits, it does not
|
|
278
|
-
retrieve records), no actions register, no verdict sidecar, no
|
|
279
|
+
retrieve records), no actions register, no verdict sidecar, no `clientSummary`, no card assembly, no
|
|
279
280
|
reviewer correction cycle, no intake-ask register.
|
|
280
281
|
|
|
281
282
|
So the checks that run are exactly those whose whole input is model-authored text —
|
package/docs/E2E.md
CHANGED
|
@@ -89,15 +89,15 @@ frozen demo profile, and the file to open to prove which profile resolved).
|
|
|
89
89
|
|
|
90
90
|
### Tier 1b — the UI PORTAL (browse the dev instance; develop UI features against it)
|
|
91
91
|
|
|
92
|
-
The pool already contains the whole UI (archive index, per-run report +
|
|
93
|
-
|
|
92
|
+
The pool already contains the whole UI (archive index, per-run report + audit workbook, per-company
|
|
93
|
+
pages); production serves it with a real web server behind the auth proxy.
|
|
94
94
|
The dev stand-in is `driver/dev-portal.mjs` — zero-dep, **loopback-only** (refuses anything else):
|
|
95
95
|
|
|
96
96
|
```bash
|
|
97
97
|
CLEAROTRON_REPORTS_DIR=$HOME/trademark-dev/pool node driver/dev-portal.mjs # http://127.0.0.1:18899/
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
- `/` → the archive index · `/<run>/report.html` → the report · `/customer/<key>/` →
|
|
100
|
+
- `/` → the archive index · `/<run>/report.html` → the report · `/customer/<key>/` → company pages
|
|
101
101
|
- `/profiles.html` + `/profiles/*` → the profile editor UI + a reverse-proxy to the profile-service
|
|
102
102
|
(run that in ITS dev mode: `PROFILE_AUTH_DISABLED=1 PROFILE_DEV=1 PROFILE_PORT=<dev port>`)
|
|
103
103
|
- the MCP HTTP face runs separately in its own dev mode (`TRADEMARK_MCP_DEV=1
|
|
@@ -115,7 +115,7 @@ so a dev instance beside a live one must be given its own (`PORTAL_PORT`, `PROFI
|
|
|
115
115
|
silently — each is a proxy to a port, and the port is all it knows. `/recipes/*` is the worse half:
|
|
116
116
|
its save endpoint writes and git-commits into whichever recipe store it reached.
|
|
117
117
|
|
|
118
|
-
A pass here looks like: index, run report, the demo
|
|
118
|
+
A pass here looks like: index, run report, the demo company's page and the profile-editor UI all
|
|
119
119
|
render against the Tier-1 pool; the `/profiles/*` proxy round-trips; traversal and non-loopback binds
|
|
120
120
|
are refused (unit-tested).
|
|
121
121
|
|
|
@@ -147,8 +147,8 @@ correct.
|
|
|
147
147
|
## Tier 3 — the paid cutover run (once, ~$40)
|
|
148
148
|
|
|
149
149
|
Same loop as Tier 2 with the real engine (`CLEAROTRON_CLAUDE_PATH=claude`) + real provider credentials +
|
|
150
|
-
a real
|
|
151
|
-
Run it once, at cutover — it bills a real
|
|
150
|
+
a real request. Validates model/vendor OUTPUT QUALITY, not machinery (Tiers 0–2 already proved that).
|
|
151
|
+
Run it once, at cutover — it bills a real clearance against real vendor credentials, so it is not a
|
|
152
152
|
loop you repeat to debug something Tier 1 could have shown you.
|
|
153
153
|
|
|
154
154
|
## Engine selection is process-wide — what that rules out
|
|
@@ -159,7 +159,7 @@ stage of every job in that activation**. There is no per-stage override and no p
|
|
|
159
159
|
|
|
160
160
|
Two round shapes this rules out, worth knowing before a plan assumes them:
|
|
161
161
|
|
|
162
|
-
- **A same-instance parallel A/B is not available.** Comparing codex against anthropic on one
|
|
162
|
+
- **A same-instance parallel A/B is not available.** Comparing codex against anthropic on one request
|
|
163
163
|
means flipping `CLEAROTRON_AI` and running the arms **sequentially**, or standing up a second instance
|
|
164
164
|
with its own env, pool and ports. Two engines cannot run concurrently under one driver.
|
|
165
165
|
- **Reviewer family diversity is not available** by routing one stage elsewhere. Sending the refutation
|
|
@@ -195,7 +195,7 @@ model-attributable — on 2026-08-15. Each failure was cheap; the sequence was n
|
|
|
195
195
|
|
|
196
196
|
**Ask whether the artifacts already separate the variables, before spending a run.** That question was
|
|
197
197
|
finally answered with no new run at all: within the runs already in hand, two axes were clean 9/9 while
|
|
198
|
-
a third faulted 9/9 — same model, same
|
|
198
|
+
a third faulted 9/9 — same model, same mark, same date, same provider. A within-run comparison
|
|
199
199
|
attributes by construction. Three runs were spent discovering that.
|
|
200
200
|
|
|
201
201
|
**Read the instrument's own vitals before you read its result.** One of the three failures was a spawn
|
package/docs/GLOSSARY.md
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Words this codebase uses in a particular way. They are here because they are already in the tree —
|
|
4
4
|
in file names, comments and test titles — and a contributor meeting one should not have to reverse
|
|
5
|
-
it out of the code. Product vocabulary a
|
|
6
|
-
|
|
5
|
+
it out of the code. Product vocabulary a user meets is in [`../README.md`](../README.md); the
|
|
6
|
+
organisation, company and project model is in [`../INSTALL.md`](../INSTALL.md) under "The four things, and
|
|
7
7
|
what contains what".
|
|
8
8
|
|
|
9
9
|
Nothing here is a rule. Each line says what the word points at, and names the file that owns it.
|
package/docs/INTAKE.md
CHANGED
|
@@ -43,11 +43,11 @@ the default agent's workspace queue.
|
|
|
43
43
|
- neither `classes` (or `marks[].classes`) **nor** a goods description (`goods`/`use`) —
|
|
44
44
|
either one suffices, and the request is not the only place classes may come from. When it
|
|
45
45
|
names none, the door resolves the same ladder the run does (request → saved search →
|
|
46
|
-
project overlay →
|
|
46
|
+
project overlay → company profile, `effective-scope.mjs`) and admits the job if any layer
|
|
47
47
|
supplies them, recording which one in a warning. A request under a project that carries the
|
|
48
48
|
classes is therefore admitted, not clarified. The clarify stands only when no layer has any,
|
|
49
49
|
and it then names what was consulted
|
|
50
|
-
- a `profileKey` that names no known
|
|
50
|
+
- a `profileKey` that names no known company (a typo must clarify, never silently
|
|
51
51
|
mis-route to the generic profile on a paid run)
|
|
52
52
|
- an unknown `product`/`recipeKey`, both selectors set at once, a `deliveryRoute`
|
|
53
53
|
outside `email | portal`, `caseLaw` or `nativeLanguage: false` (neither is a request setting —
|
|
@@ -70,7 +70,7 @@ the default agent's workspace queue.
|
|
|
70
70
|
|
|
71
71
|
Other consumed fields (see `EXAMPLE_JOB` in `enqueue-schema.mjs` for the full annotated shape):
|
|
72
72
|
`forwarderEmail`, `forwarderDomain`, `provider`, `marks[] = [{ref,name,classes}]`, `customer`
|
|
73
|
-
(applicant → self-exclusion set), `profileKey` (
|
|
73
|
+
(applicant → self-exclusion set), `profileKey` (the company → profile/framework/template),
|
|
74
74
|
`upfrontInstructions`, `brief`, `rawRequest`, `deliverableSpec`, `commercialFlexibility`,
|
|
75
75
|
`priorUse`, `deadline` (ISO-8601, drives the deadline envelope), `enqueuedAt`, and
|
|
76
76
|
`conversationId` — best-effort email-thread id consumed by the **matter dedup** below.
|
|
@@ -79,11 +79,11 @@ machinery runs:
|
|
|
79
79
|
|
|
80
80
|
- `jurisdictions` — the instructed territories, e.g. `["US","EU","JP"]`. Present ⇒ **authoritative**:
|
|
81
81
|
the matter frame is told not to widen past them, the register plan derives its regions from them and
|
|
82
|
-
the native-language lanes deepen only inside them. Omit ⇒ the project
|
|
83
|
-
codes both read; deduped case-insensitively; max 20.
|
|
82
|
+
the native-language lanes deepen only inside them. Omit ⇒ the project's or the company's
|
|
83
|
+
`defaultJurisdictions`. Names and codes both read; deduped case-insensitively; max 20.
|
|
84
84
|
- `platforms` — extra marketplaces to sweep, as bare store domains. **Additive only**: unioned onto
|
|
85
|
-
the
|
|
86
|
-
from the surface actually swept. A request can widen a
|
|
85
|
+
the company's own before the profile freeze, so the common-law grid floor and batch size re-derive
|
|
86
|
+
from the surface actually swept. A request can widen a company's mandated marketplaces and has no way
|
|
87
87
|
to narrow them. Max 10; `web` is implicit and must not be listed.
|
|
88
88
|
|
|
89
89
|
`platforms` belongs to the **clearance** pipeline alone. A knockout has no marketplace grid to add to
|
|
@@ -98,13 +98,13 @@ the instructed territories when the request names any, and "Global — all juris
|
|
|
98
98
|
names none (`driver/stages-knockout.mjs`).
|
|
99
99
|
|
|
100
100
|
Selection fields (`driver/products.mjs` declares the four): `product` (one of the four in the offering), `recipeKey` (a
|
|
101
|
-
|
|
101
|
+
company's search template — mutually exclusive with `product`), `nativeLanguage` (the one toggle, and only
|
|
102
102
|
on a Multi-country focus search — `true` adds it, and `false` is refused because it never switched
|
|
103
103
|
anything off), `geography` (`{mode: worldwide|named|account-default}` — stated,
|
|
104
104
|
because "everywhere" and "I said nothing" resolve differently), `deliveryRoute` (`email` default |
|
|
105
105
|
`portal`), `parentRunId` (escalation lineage). `caseLaw` is NOT a field: it is what a Full country
|
|
106
106
|
search IS, and sending it is refused rather than dropped. The runner's admission gate resolves
|
|
107
|
-
job → project →
|
|
107
|
+
job → project → company → **the resolved scope** and PARKS AS CLARIFY any selection this
|
|
108
108
|
build/deployment cannot run — never a silent substitution, never a drop.
|
|
109
109
|
|
|
110
110
|
### Prose sidecars (optional)
|
|
@@ -141,10 +141,10 @@ above each queue:
|
|
|
141
141
|
|
|
142
142
|
- **signature** dimension: normalized `forwarder|mark|classes|customer|ref`
|
|
143
143
|
- **thread** dimension: same `conversationId` **and** same mark (distinct marks forwarded in
|
|
144
|
-
one email are separate
|
|
144
|
+
one email are separate requests and all run)
|
|
145
145
|
|
|
146
146
|
A duplicate parks as `.duplicate` (recoverable), never runs. `"dupOverride": true` is the
|
|
147
|
-
explicit requester-confirmed force-run (it still
|
|
147
|
+
explicit requester-confirmed force-run (it is still recorded, so later true duplicates
|
|
148
148
|
are caught). A **failed** run drops its ledger entry so a genuine re-send is never blocked.
|
|
149
149
|
|
|
150
150
|
## Relevant environment
|