clearotron 0.3.1-beta.0 → 0.3.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build-info.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "ee3ad6c3a561265588b8db72f153e3eb6694d9fe",
3
- "version": "0.3.1-beta.0"
2
+ "commit": "87debabcd2a406e8a5554e5f64b20f3864c941da",
3
+ "version": "0.3.1-beta.1"
4
4
  }
@@ -1,5 +1,13 @@
1
1
  # clearotron-driver
2
2
 
3
+ ## 0.3.1-beta.1
4
+
5
+ ### Patch Changes
6
+
7
+ - c3228f3: Fixed: A clearance that stops is recorded as owing a notice even when the folder its notice is queued in cannot be written to.
8
+ - c3228f3: Fixed: Check now reports the same problems Save would refuse, so a company setting can no longer pass the check and then fail to save.
9
+ - c3228f3: Fixed: On older Windows-Subsystem installations the engine now identifies the platform by its interop registration rather than by the kernel version string.
10
+
3
11
  ## 0.3.1-beta.0
4
12
 
5
13
  ### Patch Changes
@@ -2,7 +2,7 @@
2
2
  "name": "clearotron-driver",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.1-beta.0",
5
+ "version": "0.3.1-beta.1",
6
6
  "license": "AGPL-3.0-only",
7
7
  "description": "Deterministic driver for the trademark clearance workflow: orchestration in code (fan-out, fan-in barrier, gating, retries); the model does judgment leaves only, through a reasoning CLI spawned per stage.",
8
8
  "engines": {
@@ -1146,7 +1146,19 @@ export async function knockoutInner(ctx, job, opts = {}) {
1146
1146
  // because mark_sent is the only clear and it settles a failure.json on the same evidence a delivery
1147
1147
  // needs. The marker is NOT duplicated — the primary lane's own packet lands `<runId>.failed.pending`
1148
1148
  // and the *.pending watch matches it; a second `<runId>.pending` would outlive every settle.
1149
+ // THE RECORD OF AN OBLIGATION MUST NOT BE CONTINGENT ON THE ACTION IT OBLIGES — clearance parity,
1150
+ // and the same defect this lane would have had. `writeOutboxPacket` returns null rather than throwing,
1151
+ // so an unwritable outbox reaches the raw marker write below; with the flag after it, that throw took
1152
+ // the record of the obligation with it and the run ended failed and NOT owed. Guards first, then the
1153
+ // flag, then the writes that can fail.
1149
1154
  let failPingSent = false;
1155
+ try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
1156
+ try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
1157
+ // a fresh notice supersedes an older send's skip-guards (a resumed-then-failed-again run must
1158
+ // still notify — the .sent/receipts invariant, clearance parity)
1159
+ try { writeRunStatus(ctx, { sendPending: true }); } catch (sErr) {
1160
+ note(`knockout failure notice could NOT be recorded as owed (${String(sErr?.message ?? sErr).slice(0, 100)}) — the run directory is unwritable`);
1161
+ }
1150
1162
  try {
1151
1163
  const rich = buildFailurePacket({
1152
1164
  runId, agent, job, failedStage, shortReason, terminalKind,
@@ -1163,12 +1175,7 @@ export async function knockoutInner(ctx, job, opts = {}) {
1163
1175
  mkdirSync(config.outboxDir, { recursive: true });
1164
1176
  writeFileSync(join(config.outboxDir, `${runId}.pending`), `${agent}\n`);
1165
1177
  }
1166
- // a fresh notice supersedes an older send's skip-guards (a resumed-then-failed-again run must
1167
- // still notify — the .sent/receipts invariant, clearance parity)
1168
- try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
1169
- try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
1170
- writeRunStatus(ctx, { sendPending: true });
1171
- } catch (nfErr) { note(`knockout failure-notice write skipped (${String(nfErr?.message ?? nfErr).slice(0, 100)})`); }
1178
+ } catch (nfErr) { note(`knockout failure notice is owed but its packet/marker could not be written (${String(nfErr?.message ?? nfErr).slice(0, 100)}) — status.json carries the obligation`); }
1172
1179
  note(`=== KNOCKOUT ${terminalKind === REFUSAL_TERMINAL_KIND ? "REFUSED" : "FAILED"} ${run.codename} at ${failedStage}: ${shortReason} ===\n`);
1173
1180
  // `codename`: this lane's terminals reach the SAME CLI exit as the clearance lane's, and the
1174
1181
  // CLI composes its resume line from the returned identity. Without it a knockout operator is the only
@@ -15374,6 +15374,22 @@ async function pipelineInner(job, opts = {}) {
15374
15374
  // for each id the run was known by, not this suffixed form — and the sweep re-arms off whatever is
15375
15375
  // on disk, which is how a permanent SEND PENDING was born. So: the flag always, the packet and the
15376
15376
  // marker only when nothing wrote them.
15377
+ // THE RECORD OF AN OBLIGATION MUST NOT BE CONTINGENT ON THE ACTION IT OBLIGES. The flag says
15378
+ // somebody is owed a notice. Setting it downstream of the attempt to deliver that notice means it is
15379
+ // only set when the delivery path already worked — the case that needed it least. This block did
15380
+ // exactly that for one revision: `writeOutboxPacket` swallows its own failure and returns null, so
15381
+ // an unwritable outbox fell to the branch below, where the RAW marker write threw and took the flag
15382
+ // with it. The run then ended failed and NOT owed, in the one condition where the flag is the only
15383
+ // thing that could still record the obligation. Order, therefore: supersede the old send's guards,
15384
+ // arm the flag, and only then attempt the writes that can fail.
15385
+ try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
15386
+ try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
15387
+ // A fresh notice supersedes an older send's .sent marker AND its per-channel receipts (a resumed run
15388
+ // that fails again must still notify on every channel, not be skip-guarded by the earlier send's).
15389
+ // Both are best-effort removals above, so neither can keep the flag below from being written.
15390
+ try { writeRunStatus(ctx, { sendPending: true }); } catch (sErr) {
15391
+ note(`failure notice could NOT be recorded as owed (${String(sErr?.message ?? sErr).slice(0, 100)}) — the run directory is unwritable, so nothing here can say a notice is outstanding`);
15392
+ }
15377
15393
  try {
15378
15394
  if (!failPingSent) {
15379
15395
  const packet = buildFailurePacket({
@@ -15386,15 +15402,13 @@ async function pipelineInner(job, opts = {}) {
15386
15402
  mkdirSync(config.outboxDir, { recursive: true });
15387
15403
  writeFileSync(join(config.outboxDir, `${packet.runId}.pending`), `${agent}\n`);
15388
15404
  }
15389
- // same per-send invariant as the delivery handoff: a fresh notice supersedes an older send's
15390
- // .sent marker AND its per-channel receipts (e.g. a resumed run that fails again must still
15391
- // notify on every channel, not be skip-guarded by the earlier send's receipts).
15392
- try { rmSync(join(run.runDir, ".sent")); } catch { /* none */ }
15393
- try { rmSync(driverDir(run.runDir, "send-receipts.json"), { force: true }); } catch { /* none */ }
15394
- writeRunStatus(ctx, { sendPending: true });
15395
15405
  note(`failure notice OWED (${failPingSent ? "event lane wrote the packet" : "handoff wrote the packet"}) — mark_sent is the only clear`);
15396
15406
  } catch (nfErr) {
15397
- note(`failure-notice arming skipped (${String(nfErr?.message ?? nfErr).slice(0, 100)}) .failed + status.json remain the record`);
15407
+ // NOT "status.json remains the record"that sentence was here and it was false on exactly the
15408
+ // path that reaches this catch, because the flag was written after the throw. It is written before
15409
+ // it now, so the record genuinely does stand, and saying which part failed is what keeps the next
15410
+ // reader looking in the right place.
15411
+ note(`failure notice is owed but its packet/marker could not be written (${String(nfErr?.message ?? nfErr).slice(0, 100)}) — status.json carries the obligation; the outbox does not`);
15398
15412
  }
15399
15413
  note(`=== FAILED ${run.codename} at ${failedStage}: ${reason} ===\n`);
15400
15414
  // `codename` matches the three sibling terminals (postpone, recovery park, cancelled). Without it
@@ -223,7 +223,7 @@ function renderEditor(key, p, contextPack, derived, framework, isNew) {
223
223
  <div class="row2">
224
224
  ${f("Default trademark classes", "f_classes", (p.defaultClasses||[]).join(", "), "Comma-separated Nice class numbers (e.g. 9, 41). Used only when a request doesn't list its own classes.")}
225
225
  </div>
226
- ${f("Default countries", "f_juris", (p.defaultJurisdictions||[]).join(", "), "Comma-separated country codes (e.g. US, EU, UK) that matter to this customer. A request can override or add to these.")}
226
+ ${f("Default jurisdictions", "f_juris", (p.defaultJurisdictions||[]).join(", "), "The territories that matter to this customer, comma-separated: a country name like France, or a two-letter code the engine holds such as US, GB or EU. A request can override or add to these.")}
227
227
  ${f("Email domains", "f_match", (p.matchDomains||[]).join(", "), "Email domains that automatically map a request to this customer (e.g. acme.com). Usually left empty — intake normally tags the customer directly.", undefined, true)}
228
228
  ${ta("The customer's own names", "f_excl", (p.selfExclusionOwners||[]).join("\n"), "The customer's own and affiliated company names, one per line. We never flag the customer against its own marks.", undefined, true)}
229
229
  <div class="sectionh">Default search</div>
@@ -353,8 +353,16 @@ function prettyServerError(msg) {
353
353
  [/must be CONTEXT[\s\S]*not a decision rule/i, "f_pack", "Background must be facts and concerns, not rules — rephrase any rule as a concern or a question."],
354
354
  [/exceeds the \d+-char budget/i, "f_pack", "Background is too long — keep it under 8000 characters."],
355
355
  [/delivery\.email must be/i, "f_email", "Pick a cover email format from the list."],
356
+ // THE TERRITORY REFUSAL NAMES THE OFFENDING ENTRIES ITSELF, so this maps it to the field and keeps
357
+ // the server's sentence rather than replacing it — the other rows replace jargon, and this one is
358
+ // already the plainer of the two. Unmapped, it landed in the headline list with no field marked,
359
+ // which is the half that tells a reader WHERE.
360
+ [/(is not a territory|are not territories) the engine can search/i, "f_juris",
361
+ (m) => String(m).replace(/^profiles\/[^:]+:\s*/, "")],
356
362
  ];
357
- for (const [re, id, friendly] of MAP) if (re.test(M)) return { id, friendly };
363
+ // `friendly` may be a FUNCTION where the server's own wording is better than a replacement — it
364
+ // names the offending entries, which a fixed sentence cannot.
365
+ for (const [re, id, friendly] of MAP) if (re.test(M)) return { id, friendly: typeof friendly === "function" ? friendly(M) : friendly };
358
366
  return { id:null, friendly:M };
359
367
  }
360
368
  function showServerErrors(headline, errors) {
@@ -490,7 +498,7 @@ function renderProjectEditor(customer, project, data, isNew) {
490
498
  <div class="row2">
491
499
  ${f("Default classes", "p_classes", (ov.defaultClasses||[]).join(", "), inheritHint((inh.defaultClasses||[]).join(", ")||"—"))}
492
500
  </div>
493
- ${f("Default countries", "p_juris", (ov.defaultJurisdictions||[]).join(", "), inheritHint((inh.defaultJurisdictions||[]).join(", ")||"—"))}
501
+ ${f("Default jurisdictions", "p_juris", (ov.defaultJurisdictions||[]).join(", "), inheritHint((inh.defaultJurisdictions||[]).join(", ")||"—"))}
494
502
  <div class="field"><label for="p_email">Cover email format</label><div class="hint">Inherit, or set just for this project.</div>
495
503
  <select id="p_email"><option value=""${!d?" selected":""}>Inherit</option><option value="summary"${d&&d.email!=="table"?" selected":""}>Summary</option><option value="table"${d&&d.email==="table"?" selected":""}>Table</option></select></div>
496
504
  <div class="field"><label for="p_priv">Confidentiality marking</label><div class="hint">Only applies when a format is set above.</div><select id="p_priv"><option value=""${d&&d.privileged===false?"":" selected"}>Privileged &amp; Confidential</option><option value="no"${d&&d.privileged===false?" selected":""}>No marking</option></select></div>
@@ -3904,8 +3904,8 @@
3904
3904
  "todos": 0
3905
3905
  },
3906
3906
  "runner.failure-backstop.test.mjs": {
3907
- "tests": 3,
3908
- "asserts": 25,
3907
+ "tests": 4,
3908
+ "asserts": 29,
3909
3909
  "skips": 0,
3910
3910
  "todos": 0
3911
3911
  },
@@ -5737,6 +5737,12 @@
5737
5737
  "skips": 0,
5738
5738
  "todos": 0
5739
5739
  },
5740
+ "checkAndSaveCannotDisagree.test.ts": {
5741
+ "tests": 2,
5742
+ "asserts": 6,
5743
+ "skips": 0,
5744
+ "todos": 0
5745
+ },
5740
5746
  "clearancesKeepsItsControls.test.ts": {
5741
5747
  "tests": 4,
5742
5748
  "asserts": 8,
@@ -1,5 +1,9 @@
1
1
  # trademark-artifacts-mcp
2
2
 
3
+ ## 0.3.1-beta.1
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.1-beta.0
4
8
 
5
9
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-artifacts-mcp",
3
- "version": "0.3.1-beta.0",
3
+ "version": "0.3.1-beta.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "MCP server to interrogate clearotron trademark-clearance runs — list/read artifacts, trace the full decision flow, telemetry/cost, coverage, single-run search, and a gated single-step what-if. Imports the clearotron-driver read-only; touches no driver/template/deploy files.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clearotron",
3
3
  "type": "module",
4
- "version": "0.3.1-beta.0",
4
+ "version": "0.3.1-beta.1",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18670,7 +18670,7 @@ var PROFILE_FIELDS = [
18670
18670
  item: {
18671
18671
  ok: isTerritoryEntry,
18672
18672
  strict: true,
18673
- expected: "a territory from the picker below, or a two-letter code"
18673
+ expected: "a territory from the picker below, or a two-letter country code the engine holds, like US, GB or EU"
18674
18674
  },
18675
18675
  hint: "One per line or comma-separated — pick from the list below, or type a two-letter code like US, EU or GB; both are understood. A region counts as one entry: European Union covers its member states, so there is no need to add them. Anything the engine cannot search is refused rather than stored, because a stored one is a default that quietly does nothing."
18676
18676
  },
@@ -20572,6 +20572,15 @@ function Profile({ ctx }) {
20572
20572
  return;
20573
20573
  }
20574
20574
  if (action === "validate") {
20575
+ const errors = Array.isArray(r.value["errors"]) ? r.value["errors"] : [];
20576
+ if (r.value["ok"] === false || errors.length) {
20577
+ setChecked(false);
20578
+ setProblem({
20579
+ title: errors.length === 1 ? "1 thing to fix before saving:" : `${errors.length} things to fix before saving:`,
20580
+ lines: errors.length ? errors : ["This company’s settings were refused, and no reason came back."]
20581
+ });
20582
+ return;
20583
+ }
20575
20584
  setChecked(true);
20576
20585
  return;
20577
20586
  }
@@ -21588,6 +21597,16 @@ function ProjectEditor({ account, project, mayChange, onBack }) {
21588
21597
  return;
21589
21598
  }
21590
21599
  if (action === "validate") {
21600
+ const dryRun = "value" in r && r.value && typeof r.value === "object" ? r.value : {};
21601
+ const errors = Array.isArray(dryRun["errors"]) ? dryRun["errors"] : [];
21602
+ if (dryRun["ok"] === false || errors.length) {
21603
+ setChecked(false);
21604
+ setProblem({
21605
+ title: "That cannot be saved as written",
21606
+ lines: errors.length ? errors : ["This project was refused, and no reason came back."]
21607
+ });
21608
+ return;
21609
+ }
21591
21610
  setChecked(true);
21592
21611
  return;
21593
21612
  }
@@ -49,7 +49,7 @@
49
49
  -->
50
50
  <link rel="preconnect" href="https://api.fontshare.com" crossorigin />
51
51
  <link href="https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700,900&display=swap" rel="stylesheet" />
52
- <script type="module" crossorigin src="/portal/assets/index-CwPAS0we.js"></script>
52
+ <script type="module" crossorigin src="/portal/assets/index-DLa7PlKY.js"></script>
53
53
  <link rel="stylesheet" crossorigin href="/portal/assets/index-Cv-E_agg.css">
54
54
  </head>
55
55
  <body>
@@ -2,7 +2,7 @@
2
2
  "name": "portal-ui",
3
3
  "private": true,
4
4
  "type": "module",
5
- "version": "0.3.1-beta.0",
5
+ "version": "0.3.1-beta.1",
6
6
  "license": "AGPL-3.0-only",
7
7
  "description": "The unified trademark portal UI. One address, one login: who you are decides what you see. Built as a static bundle, served by driver/portal-service.mjs — the browser never reaches profile-service or recipe-service.",
8
8
  "engines": {
@@ -1,5 +1,9 @@
1
1
  # trademark-oauth-mcp-bridge
2
2
 
3
+ ## 0.3.1-beta.1
4
+
5
+ No changes in this release.
6
+
3
7
  ## 0.3.1-beta.0
4
8
 
5
9
  No changes in this release.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trademark-oauth-mcp-bridge",
3
- "version": "0.3.1-beta.0",
3
+ "version": "0.3.1-beta.1",
4
4
  "license": "AGPL-3.0-only",
5
5
  "private": true,
6
6
  "description": "OAuth 2.1 MCP stdio bridge used by the engine's case-law gather stage (courtlistener / legaldatahunter).",