@junghanacs/entwurf 0.50.0 → 0.51.0

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
- * entwurf-v2-contract-schema — the pi-side TypeBox REPRESENTATION of the frozen
3
- * `entwurf_v2` contract. This module exists ONLY to carry the pi-ai TypeBox
4
- * schema builders (`StringEnum`, `Type`) out of the pi-free core
2
+ * entwurf-v2-contract-schema — the pi-side JSON Schema REPRESENTATION of the frozen
3
+ * `entwurf_v2` contract. This module exists ONLY to carry the pi schema builders
4
+ * (pi-ai's `StringEnum`, pi-schema's `obj`/`str`/…) out of the pi-free core
5
5
  * (`entwurf-v2-contract.ts`); the constants/types/decision logic live there.
6
6
  *
7
7
  * ⚠️ pi LANE ONLY — the MCP bridge (`mcp/entwurf-bridge/src/index.ts`) MUST NOT
@@ -11,18 +11,21 @@
11
11
  * the contract CONSTANTS + `resolveDispatch` (pi-free core), never these schemas.
12
12
  * Consumer: `check-entwurf-v2-contract` ONLY. This is a gate-side REPRESENTATION of the
13
13
  * frozen contract, not a shipped tool schema — measured 2026-07-27, nothing else in the
14
- * import graph reaches it. The two surfaces a model actually reads build their own:
15
- * `pi-extensions/entwurf-control.ts` (TypeBox `entwurfV2Parameters` + tool description) and
16
- * `mcp/entwurf-bridge/src/index.ts` (Zod). Change a model-facing description THERE; changing
17
- * it here reaches no model, and `check-entwurf-v2-surface` is what pins those two.
14
+ * import graph reaches it. The surface a model actually reads builds its own:
15
+ * `mcp/entwurf-bridge/src/index.ts` (Zod, descriptions from ENTWURF.json), which a pi citizen
16
+ * reaches through Pi's built-in MCP since the native pi tools were retired (2f7c883, Pi 0.99).
17
+ * Change a model-facing description THERE; changing it here reaches no model.
18
18
  *
19
- * StringEnum (typebox 1.x) inside Type.Object (typebox 0.34) — the same mix the
20
- * existing entwurf tools use. The logic types in the core are hand-written unions,
21
- * NOT `Static<>` inferences, so the 0.34/1.x widening caveat does not touch them;
22
- * `check-entwurf-v2-contract` keeps these schemas and the core types in lockstep.
19
+ * Pi SDK 1.5 (#155) removed pi-ai's TypeBox re-export (`Type`); its builders are now
20
+ * `@earendil-works/pi-schema`, which emits plain JSON Schema (obj() closed by default,
21
+ * opt() leaves a property out of `required`, lit() is `{const}`), and pi-ai's
22
+ * `StringEnum` still emits `{type:"string", enum}`. The logic types in the core are
23
+ * hand-written unions, NOT `Static<>` inferences; `check-entwurf-v2-contract` keeps
24
+ * these schemas and the core types in lockstep.
23
25
  */
24
26
 
25
- import { StringEnum, Type } from "@earendil-works/pi-ai";
27
+ import { StringEnum } from "@earendil-works/pi-ai";
28
+ import { bool, lit, nul, obj, opt, type Properties, type Schema, str, union } from "@earendil-works/pi-schema";
26
29
  import {
27
30
  ENTWURF_INTENTS,
28
31
  ENTWURF_V2_ACTIONS,
@@ -34,36 +37,43 @@ import {
34
37
  } from "./entwurf-v2-contract.ts";
35
38
  import { SESSION_ID_RE } from "./session-id.js";
36
39
 
37
- export const EntwurfV2InputSchema = Type.Object(
38
- {
39
- // R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
40
- // a malformed/typo gid fails the schema (→ bad-target) and can never reach a
41
- // spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
42
- target: Type.String({
43
- pattern: SESSION_ID_RE.source,
40
+ /**
41
+ * The face the drift guard reads. pi-schema types a schema as `{[keyword]: unknown}`; obj() always emits
42
+ * `properties`, so this names that one keyword without asserting anything else about the shape.
43
+ */
44
+ type ObjectSchema = Schema & { readonly properties: Readonly<Record<string, Schema>> };
45
+
46
+ /** obj() — closed, so it emits `additionalProperties: false` — under the face above. */
47
+ const closedObject = (properties: Properties): ObjectSchema => obj(properties) as ObjectSchema;
48
+
49
+ export const EntwurfV2InputSchema = closedObject({
50
+ // R2/F6 executable: the garden-id shape is enforced by pattern, not prose —
51
+ // a malformed/typo gid fails the schema (→ bad-target) and can never reach a
52
+ // spawn. SSOT regex = SESSION_ID_RE (pi-extensions/lib/session-id.js).
53
+ target: str({
54
+ pattern: SESSION_ID_RE.source,
55
+ description:
56
+ "garden-id of an EXISTING citizen (pattern-enforced). Fresh sibling creation is out of scope on every current surface — there is no fallback verb that still spawns. A malformed/typo gid is bad-target, never a new spawn.",
57
+ }),
58
+ intent: StringEnum(ENTWURF_INTENTS, {
59
+ description:
60
+ "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
61
+ }),
62
+ mode: opt(
63
+ StringEnum(ENTWURF_V2_MODES, {
44
64
  description:
45
- "garden-id of an EXISTING citizen (pattern-enforced). Fresh sibling creation is out of scope on every current surface — there is no fallback verb that still spawns. A malformed/typo gid is bad-target, never a new spawn.",
65
+ "delivery mode — NOT the ownership axis (F1) nor liveness routing. steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
46
66
  }),
47
- intent: StringEnum(ENTWURF_INTENTS, {
48
- description:
49
- "caller's declared outcome contract (F1). ONE member since the visible-first cut: fire-and-forget = ack only. The second, owned-outcome (caller owns completion), resumed a dormant citizen by launching a hidden background child and was withdrawn — a dormant citizen is currently unreachable by this verb.",
67
+ ),
68
+ wantsReply: opt(
69
+ bool({
70
+ description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
50
71
  }),
51
- mode: Type.Optional(
52
- StringEnum(ENTWURF_V2_MODES, {
53
- description:
54
- "delivery mode — NOT the ownership axis (F1) nor liveness routing. steer (ask the receiver's steering queue) or follow_up (ask its follow-up queue). NEITHER is an interrupt: a busy receiver drains steering after each turn and follow-ups only when its inner loop ends, so a later steer overtakes every earlier follow_up and there is no order between the two. Both queues are volatile process memory — an abort drops what is in them. The receipt names which queue accepted the message (`queued-steer` / `queued-follow-up`); an idle receiver answers `sent`, meaning the turn was TRIGGERED, not that the model has seen the text. MEANINGLESS on the meta-mailbox transport (F-mailbox): a mailbox ack is enqueue+doorbell, not a turn injection, so steer/follow_up does not apply when the verdict transport is meta-mailbox.",
55
- }),
56
- ),
57
- wantsReply: Type.Optional(
58
- Type.Boolean({
59
- description: "conversation etiquette only — NOT ownership; never triggers an auto-send (Q2).",
60
- }),
61
- ),
62
- // `additionalProperties: false` — a frozen contract input is exact; an unknown
63
- // key is a caller error, not silently ignored.
64
- },
65
- { additionalProperties: false },
66
- );
72
+ ),
73
+ // `additionalProperties: false` — a frozen contract input is exact; an unknown
74
+ // key is a caller error, not silently ignored. obj() is closed by default (it
75
+ // emits `additionalProperties: false` unless `open: true`).
76
+ });
67
77
 
68
78
  // Receipt = a DISCRIMINATED union on `ok` (R3/F6) — NOT one flat object with
69
79
  // optionals. Each branch is EXACT (`additionalProperties: false`): without it,
@@ -72,35 +82,29 @@ export const EntwurfV2InputSchema = Type.Object(
72
82
  // With it, success carries action/transport/ownership and rejects a stray reason;
73
83
  // reject carries reason and rejects any allow facet — the branches are mutually
74
84
  // exclusive at the schema level, not merely by declared-property convention.
75
- export const EntwurfV2ReceiptSuccessSchema = Type.Object(
76
- {
77
- ok: Type.Literal(true),
78
- action: StringEnum(ENTWURF_V2_ACTIONS),
79
- transport: StringEnum(ENTWURF_V2_TRANSPORTS),
80
- ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
81
- observedLiveness: StringEnum(FACT_LIVENESSES, {
82
- description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
83
- }),
84
- },
85
- { additionalProperties: false },
86
- );
85
+ export const EntwurfV2ReceiptSuccessSchema = closedObject({
86
+ ok: lit(true),
87
+ action: StringEnum(ENTWURF_V2_ACTIONS),
88
+ transport: StringEnum(ENTWURF_V2_TRANSPORTS),
89
+ ownership: StringEnum(ENTWURF_V2_OWNERSHIPS),
90
+ observedLiveness: StringEnum(FACT_LIVENESSES, {
91
+ description: "the 4-value fact liveness the verdict was computed from (R1/R3).",
92
+ }),
93
+ });
87
94
 
88
- export const EntwurfV2ReceiptRejectSchema = Type.Object(
89
- {
90
- ok: Type.Literal(false),
91
- reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
92
- // ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
93
- // key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
94
- // and a real FactLiveness otherwise. Optional would lose the "key always
95
- // present, value may be null" shape and weaken the discriminated union; the
96
- // reason-dependent null/non-null rule is enforced semantically (the gate's
97
- // rejectObservedLivenessWellFormed fixture), not by this blanket union.
98
- observedLiveness: Type.Union([StringEnum(FACT_LIVENESSES), Type.Null()], {
99
- description:
100
- "the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
101
- }),
102
- },
103
- { additionalProperties: false },
104
- );
95
+ export const EntwurfV2ReceiptRejectSchema = closedObject({
96
+ ok: lit(false),
97
+ reason: StringEnum(ENTWURF_V2_REJECT_REASONS),
98
+ // ?6: required-nullable, NOT optional — a reject branch ALWAYS carries the
99
+ // key, and it is `null` for the pre-probe rejects (PRE_PROBE_REJECT_REASONS)
100
+ // and a real FactLiveness otherwise. Optional would lose the "key always
101
+ // present, value may be null" shape and weaken the discriminated union; the
102
+ // reason-dependent null/non-null rule is enforced semantically (the gate's
103
+ // rejectObservedLivenessWellFormed fixture), not by this blanket union.
104
+ observedLiveness: union([StringEnum(FACT_LIVENESSES), nul()], {
105
+ description:
106
+ "the 4-value fact liveness the reject was computed from (R1/R3); null for the pre-probe rejects (bad-target / target-locked / target-address-conflict) where no probe ran.",
107
+ }),
108
+ });
105
109
 
106
- export const EntwurfV2ReceiptSchema = Type.Union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
110
+ export const EntwurfV2ReceiptSchema = union([EntwurfV2ReceiptSuccessSchema, EntwurfV2ReceiptRejectSchema]);
@@ -3,7 +3,7 @@
3
3
  * verb (0.11 Stage 0 step 4-pre / 동결결정 10). PURE pi-FREE core: the
4
4
  * intent×liveness decision table + the reject taxonomy + a pure resolver.
5
5
  * NO runtime dispatch, NO spawn/send, NO I/O — step 5 wires this to transports.
6
- * The pi-ai TypeBox REPRESENTATION of this contract lives in the separate
6
+ * The pi-schema REPRESENTATION of this contract lives in the separate
7
7
  * `entwurf-v2-contract-schema.ts` (0.12.1 B-1) so this module — which the
8
8
  * harness-neutral MCP bridge reaches at boot — carries no pi dependency.
9
9
  *
@@ -516,9 +516,9 @@ export function resolveNativePushDispatch(intent: EntwurfIntent, liveness: Nativ
516
516
  };
517
517
  }
518
518
 
519
- // ── TypeBox schemas ────────────────────────────────────────────────────────
520
- // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi-ai TypeBox
521
- // builders (StringEnum/Type) are a pi-lane dependency, so they cannot live in
519
+ // ── JSON schemas ───────────────────────────────────────────────────────────
520
+ // MOVED to `entwurf-v2-contract-schema.ts` (0.12.1 B-1): the pi schema
521
+ // builders (pi-ai StringEnum, pi-schema since #155) are a pi-lane dependency, so they cannot live in
522
522
  // this pi-free core — the MCP bridge reaches this module at boot and must stay
523
523
  // harness-neutral (check-entwurf-bridge-pi-free). The schemas import the
524
524
  // constants/types above; pi-side consumers import the schemas from there.
@@ -349,7 +349,7 @@ export interface PiDurableRoots {
349
349
  // ---------------------------------------------------------------------------
350
350
  // Durable surface, typed NARROWLY and locally — read at pi-durable 1.0.2 @cd32f77 (#129,
351
351
  // `harness/types.ts`; historical coordinates, not re-anchored here). The current supply is the
352
- // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.4.0 SDK set
352
+ // carrier emitted from Entwurf's owned source (pi/pi-durable/src, #141) against the 1.5.0 SDK set
353
353
  // `pi/pi-durable/overlay/upstream-pin.json` names (origin `abe508e`). `[read 2026-10-13]` at
354
354
  // v1.2.0 one shape typed below moved: `ToolExecutionResult.content` is now `output`, beside the new
355
355
  // `structuredOutput` (`harness/types.ts`, its CHANGELOG's breaking changes). An omitted `output`
package/run.sh CHANGED
@@ -1120,7 +1120,7 @@ check_entwurf_v2_contract() {
1120
1120
  # visible-first cut, single verdict per cell, 1 allow / 2 reject. N1
1121
1121
  # indeterminate never dispatches, and nothing on any cell starts a process.
1122
1122
  # R5 taxonomy covers table reasons + pre-claims bad-target/target-locked. Plus
1123
- # a schema↔types drift guard on the TypeBox input/receipt. Pure, no API.
1123
+ # a schema↔types drift guard on the pi-schema input/receipt. Pure, no API.
1124
1124
  run_ts scripts/check-entwurf-v2-contract.ts
1125
1125
  }
1126
1126
 
@@ -2340,21 +2340,64 @@ function stripBumpLedger(file, text) {
2340
2340
  return kept.join('\n');
2341
2341
  }
2342
2342
 
2343
- let rangeDecls = 0, exactDecls = 0;
2343
+ // PUBLISHED-SCOPE REGIONS (#155). Between an SDK bump in this checkout and the release that ships it,
2344
+ // an operator doc must keep telling a reader how to install the package that IS on npm — whose Pi
2345
+ // range is the previous floor — while the checkout's own sentences already name the new one. Those
2346
+ // are two different subjects in one file, so the doc marks the published one explicitly:
2347
+ // <!-- entwurf:published @junghanacs/entwurf@<version> pi <sdk> --> … <!-- /entwurf:published -->
2348
+ // Inside a region every scan below binds to <sdk>, outside it to the pinned floor. The region is not
2349
+ // a skip: it is scanned, it must hold at least one declaration, and after the four-surface assertion
2350
+ // below the published-scope claim holds its pair to PUBLISHED_ARTIFACT — a published package's own
2351
+ // manifest, frozen here. It is deliberately NOT derived from this checkout (package.json's version,
2352
+ // the workspace closure): a published package's contract stays true when the checkout moves on, so a
2353
+ // release preparation must not falsify it. `[측정 2026-11-03]` the source-only qualification snapshot
2354
+ // has no .git, so a release tag cannot be that oracle either.
2355
+ const PUBLISHED_OPEN = /<!-- entwurf:published @junghanacs\/entwurf@(\d+\.\d+\.\d+) pi (\d+\.\d+\.\d+) -->/g;
2356
+ const PUBLISHED_CLOSE = '<!-- /entwurf:published -->';
2357
+ function publishedRegions(text) {
2358
+ const regions = [], problems = [];
2359
+ let from = 0;
2360
+ for (const open of text.matchAll(PUBLISHED_OPEN)) {
2361
+ if (open.index < from) { problems.push(`a region opens inside the previous one at offset ${open.index}`); continue; }
2362
+ const end = text.indexOf(PUBLISHED_CLOSE, open.index);
2363
+ const next = text.slice(open.index + open[0].length).search(/<!-- entwurf:published /);
2364
+ if (end < 0 || (next >= 0 && open.index + open[0].length + next < end)) { problems.push(`the region opened by "${open[0]}" is not closed before the next one`); continue; }
2365
+ regions.push({ start: open.index, end, version: open[1], sdk: open[2], body: text.slice(open.index, end) });
2366
+ from = end + PUBLISHED_CLOSE.length;
2367
+ }
2368
+ const opens = [...text.matchAll(/<!-- entwurf:published /g)].length;
2369
+ const closes = text.split(PUBLISHED_CLOSE).length - 1;
2370
+ if (opens !== regions.length + problems.length) problems.push(`${opens - regions.length} published marker(s) do not have the exact opening shape`);
2371
+ if (closes !== regions.length) problems.push(`${closes} closing marker(s) for ${regions.length} region(s)`);
2372
+ return { regions, problems };
2373
+ }
2374
+ const floorAt = (regions, index) => regions.find((r) => index > r.start && index < r.end)?.sdk ?? piAi;
2375
+ const nextMinor = (v) => { const [a, b] = v.split('.').map(Number); return `${a}.${b + 1}`; };
2376
+
2377
+ let rangeDecls = 0, exactDecls = 0, sourceRangeDecls = 0;
2344
2378
  for (const file of BASELINE_DOCS) {
2345
2379
  const text = stripBumpLedger(file, readFileSync(file, 'utf8'));
2380
+ const { regions } = publishedRegions(text);
2346
2381
  // Closed-range declarations: `>=<floor> <<major>.<ceiling>` (spaces optional).
2347
- for (const [decl, floor, ceilMajor, ceilMinor] of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?(\d+)\.(\d+)/g)) {
2382
+ for (const m of text.matchAll(/>=\s?(\d+\.\d+\.\d+)\s?<\s?(\d+)\.(\d+)/g)) {
2383
+ const [decl, floor, ceilMajor, ceilMinor] = m;
2384
+ const want = floorAt(regions, m.index);
2348
2385
  rangeDecls++;
2349
- assert.equal(floor, piAi,
2350
- `${file}: declared pi floor in "${decl}" is ${floor}, but the pinned SDK is ${piAi} — a baseline doc may not advertise a version no gate drives`);
2351
- assert.equal(`${ceilMajor}.${ceilMinor}`, `${piMaj}.${piMin + 1}`,
2352
- `${file}: declared pi ceiling in "${decl}" must be the next minor (${piMaj}.${piMin + 1})`);
2386
+ if (want === piAi) sourceRangeDecls++;
2387
+ assert.equal(floor, want, want === piAi
2388
+ ? `${file}: declared pi floor in "${decl}" is ${floor}, but the pinned SDK is ${piAi} — a baseline doc may not advertise a version no gate drives`
2389
+ : `${file}: declared pi floor in "${decl}" is ${floor}, but its published-scope region names pi ${want}`);
2390
+ assert.equal(`${ceilMajor}.${ceilMinor}`, nextMinor(want),
2391
+ `${file}: declared pi ceiling in "${decl}" must be the next minor (${nextMinor(want)})`);
2353
2392
  }
2354
2393
  // Exact install pins: `@earendil-works/pi-<pkg>@<version>`.
2355
- for (const [decl, ver] of text.matchAll(/@earendil-works\/pi-(?:ai|coding-agent|tui)@(\d+\.\d+\.\d+)/g)) {
2394
+ for (const m of text.matchAll(/@earendil-works\/pi-(?:ai|coding-agent|tui)@(\d+\.\d+\.\d+)/g)) {
2395
+ const [decl, ver] = m;
2396
+ const want = floorAt(regions, m.index);
2356
2397
  exactDecls++;
2357
- assert.equal(ver, piAi, `${file}: install example "${decl}" pins ${ver}, but the pinned SDK is ${piAi}`);
2398
+ assert.equal(ver, want, want === piAi
2399
+ ? `${file}: install example "${decl}" pins ${ver}, but the pinned SDK is ${piAi}`
2400
+ : `${file}: install example "${decl}" pins ${ver}, but its published-scope region names pi ${want}`);
2358
2401
  }
2359
2402
  }
2360
2403
  // Prose declarations carry the pin in sentences the two patterns above cannot
@@ -2366,9 +2409,11 @@ const PROSE_DECLS = [
2366
2409
  ['ROADMAP.md', /floor = \*\*(\d+\.\d+\.\d+)\*\*/, 'floor = **<version>**'],
2367
2410
  ];
2368
2411
  for (const [file, re, shape] of PROSE_DECLS) {
2369
- const m = readFileSync(file, 'utf8').match(re);
2412
+ const text = readFileSync(file, 'utf8');
2413
+ const m = text.match(re);
2370
2414
  assert.ok(m, `${file}: the baseline sentence "${shape}" is gone — restore it or update check-dep-versions; a doc reword must not silently drop the pin from the gate`);
2371
- assert.equal(m[1], piAi, `${file}: "${shape}" declares ${m[1]}, but the pinned SDK is ${piAi}`);
2415
+ const want = floorAt(publishedRegions(text).regions, m.index);
2416
+ assert.equal(m[1], want, `${file}: "${shape}" declares ${m[1]}, but the ${want === piAi ? 'pinned SDK' : 'published-scope region'} is ${want}`);
2372
2417
  }
2373
2418
  // ── #120 P4: ONE FLOOR, FOUR MECHANICAL SURFACES ──────────────────────────
2374
2419
  // The bump is mostly a number substitution, and that is exactly how a bump goes
@@ -2390,6 +2435,8 @@ const PI_CONSTELLATION = [
2390
2435
  '@earendil-works/pi-codemode', '@earendil-works/pi-mcp',
2391
2436
  // #130: the durable library the pi-durable carrier imports — a production dependency since 0.32.
2392
2437
  '@earendil-works/pi-durable',
2438
+ // `[측정 2026-11-03]` 1.5.0 (#155): pi-schema joins — declared by chord, pi-agent-core, pi-ai and pi-durable.
2439
+ '@earendil-works/pi-schema',
2393
2440
  ];
2394
2441
  const sameSet = (got, want, where) => {
2395
2442
  const missing = want.filter((n) => !got.includes(n));
@@ -2404,7 +2451,7 @@ for (const [, name, ver] of runSh.matchAll(/"(@earendil-works\/[a-z-]+)@(\d+\.\d
2404
2451
  if (ver !== piAi) floorGaps.push(`run.sh install constellation: ${name}@${ver} is not the floor ${piAi}`);
2405
2452
  }
2406
2453
  sameSet(installed, PI_CONSTELLATION, 'run.sh install constellation');
2407
- // pnpm-workspace.yaml minimumReleaseAgeExclude: the same eight, each ADMITTING the
2454
+ // pnpm-workspace.yaml minimumReleaseAgeExclude: the same constellation, each ADMITTING the
2408
2455
  // current floor. History stays (`0.85.0 || 0.85.1 || …`) — only the newest term is
2409
2456
  // the contract, because that is what an install resolves against.
2410
2457
  const closure = [...readFileSync('pnpm-workspace.yaml', 'utf8').matchAll(/^\s*-\s*'(@earendil-works\/[a-z-]+)@([^']+)'/gm)];
@@ -2437,22 +2484,64 @@ const CURRENT_CONTRACT_DECLS = [
2437
2484
  ['docs/acp-backend-rail.md', /SDK exact `(\d+\.\d+\.\d+)`, host `(>=[\d.]+ <\d+\.\d+)`/, "the ACP support row's exact+range pair"],
2438
2485
  ];
2439
2486
  for (const [file, re, shape] of CURRENT_CONTRACT_DECLS) {
2440
- const m = readFileSync(file, 'utf8').match(re);
2487
+ const text = readFileSync(file, 'utf8');
2488
+ const m = text.match(re);
2441
2489
  if (!m) { floorGaps.push(`${file}: ${shape} is gone — a reworded current-contract sentence must fail loud, not drop out silently`); continue; }
2490
+ // A sentence inside a published-scope region states the published package's contract; it is
2491
+ // held to that region's pi here and the region itself to its oracles below.
2492
+ const floor = floorAt(publishedRegions(text).regions, m.index);
2493
+ const range = `>=${floor} <${nextMinor(floor)}`;
2442
2494
  for (const captured of m.slice(1)) {
2443
- const expected = captured.startsWith('>=') ? nextFloorRange : piAi;
2444
- if (captured !== expected) floorGaps.push(`${file}: ${shape} declares "${captured}", but the floor is ${piAi} (${nextFloorRange})`);
2495
+ const expected = captured.startsWith('>=') ? range : floor;
2496
+ if (captured !== expected) floorGaps.push(`${file}: ${shape} declares "${captured}", but the ${floor === piAi ? 'floor' : 'published-scope region'} is ${floor} (${range})`);
2445
2497
  }
2446
2498
  }
2447
2499
  assert.deepEqual(floorGaps, [],
2448
2500
  `[QK:PI-FLOOR-SINGLE-SOURCE] the pi floor is declared on four mechanical surfaces — the durable pin's SDK set with package.json's exact production dependencies, run.sh's install constellation, the pnpm workspace closure, and the current-contract sentences in the docs — and every one of them must name the SAME floor. A bump that moves three of the four is the ordinary way this breaks, and the surface left behind is the one nothing else reads`);
2449
2501
 
2502
+ // The published-scope regions' own oracle (see PUBLISHED-SCOPE REGIONS above). Checked after the
2503
+ // four surfaces so a stranded closure row is still attributed to the claim above.
2504
+ // PUBLISHED_ARTIFACT is the one published pair a region may name, copied from the artifact, not from
2505
+ // this checkout. `[측정 2026-11-03]` the global install of @junghanacs/entwurf@0.50.0 whose bytes #155
2506
+ // HOP1 accepted against the registry tarball declares exactly these nine @earendil-works dependencies
2507
+ // in its package.json (sha256 20c572534fcfc03e8f89890a0853a25048166f7c998e433945289a5d20ee1234).
2508
+ const PUBLISHED_ARTIFACT = {
2509
+ version: '0.50.0',
2510
+ dependencies: {
2511
+ '@earendil-works/chord': '1.4.0', '@earendil-works/pi-agent-core': '1.4.0', '@earendil-works/pi-ai': '1.4.0',
2512
+ '@earendil-works/pi-codemode': '1.4.0', '@earendil-works/pi-coding-agent': '1.4.0', '@earendil-works/pi-durable': '1.4.0',
2513
+ '@earendil-works/pi-mcp': '1.4.0', '@earendil-works/pi-telemetry': '1.4.0', '@earendil-works/pi-tui': '1.4.0',
2514
+ },
2515
+ };
2516
+ const publishedSdk = [...new Set(Object.values(PUBLISHED_ARTIFACT.dependencies))];
2517
+ const publishedGaps = [];
2518
+ if (publishedSdk.length !== 1) publishedGaps.push(`PUBLISHED_ARTIFACT ${PUBLISHED_ARTIFACT.version} carries no single SDK generation (${publishedSdk.join(', ')})`);
2519
+ let publishedRegionCount = 0;
2520
+ const PUBLISHED_REGION_MAX_LINES = 40;
2521
+ for (const file of [...new Set([...BASELINE_DOCS, ...CURRENT_CONTRACT_DECLS.map(([f]) => f)])]) {
2522
+ const { regions, problems } = publishedRegions(readFileSync(file, 'utf8'));
2523
+ for (const p of problems) publishedGaps.push(`${file}: ${p}`);
2524
+ for (const r of regions) {
2525
+ publishedRegionCount++;
2526
+ const where = `${file}: region "@junghanacs/entwurf@${r.version} pi ${r.sdk}"`;
2527
+ if (r.version !== PUBLISHED_ARTIFACT.version) publishedGaps.push(`${where} names a package no frozen published manifest records (only @junghanacs/entwurf@${PUBLISHED_ARTIFACT.version})`);
2528
+ else if (publishedSdk.length !== 1 || r.sdk !== publishedSdk[0]) publishedGaps.push(`${where} names pi ${r.sdk}, but the published ${r.version} manifest ships pi ${publishedSdk.join(' | ')}`);
2529
+ const decls = [...r.body.matchAll(/>=\s?\d+\.\d+\.\d+\s?<\s?\d+\.\d+|@earendil-works\/pi-(?:ai|coding-agent|tui)@\d+\.\d+\.\d+/g)].length;
2530
+ if (decls === 0) publishedGaps.push(`${where} holds no pi declaration — an empty region only hides prose from the scan`);
2531
+ const lines = r.body.split('\n').length;
2532
+ if (lines > PUBLISHED_REGION_MAX_LINES) publishedGaps.push(`${where} spans ${lines} lines (max ${PUBLISHED_REGION_MAX_LINES}) — mark the table, example or sentence, not the section around it`);
2533
+ }
2534
+ }
2535
+ if (publishedRegionCount > 0 && sourceRangeDecls < 2) publishedGaps.push(`only ${sourceRangeDecls} range declaration(s) remain outside published-scope regions — the checkout's own floor must still be declared where this gate reads it`);
2536
+ assert.deepEqual(publishedGaps, [],
2537
+ `[QK:PI-FLOOR-PUBLISHED-SCOPE] a published-scope region in a baseline doc names a published package and the SDK its frozen manifest ships (PUBLISHED_ARTIFACT, independent of this checkout's version and closure), holds at least one declaration, stays narrow, and leaves the checkout's floor declared outside it`);
2538
+
2450
2539
  // Guard the guard: if the patterns ever stop matching, the loops above pass
2451
2540
  // vacuously and the docs fall back OUT of the gate without a word.
2452
2541
  assert.ok(rangeDecls >= 5, `expected at least 5 pi range declarations across the baseline docs, found ${rangeDecls} — the doc scan matched (almost) nothing and would pass vacuously`);
2453
2542
  assert.ok(exactDecls >= 1, `expected at least 1 exact pi install pin in the baseline docs, found ${exactDecls}`);
2454
2543
 
2455
- console.log(`[check-dep-versions] ok — pi ${piAi} is coherent across the durable pin's SDK set, package.json (exact production dependencies, no dev/peer duplicate), run.sh (install pins), and ${BASELINE_DOCS.length} baseline docs (${rangeDecls} range + ${exactDecls} exact + ${PROSE_DECLS.length} prose declarations)`);
2544
+ console.log(`[check-dep-versions] ok — pi ${piAi} is coherent across the durable pin's SDK set, package.json (exact production dependencies, no dev/peer duplicate), run.sh (install pins), and ${BASELINE_DOCS.length} baseline docs (${rangeDecls} range + ${exactDecls} exact + ${PROSE_DECLS.length} prose declarations; ${publishedRegionCount} published-scope region(s))`);
2456
2545
  EOF
2457
2546
  )
2458
2547
  }
@@ -4040,7 +4129,7 @@ check_pack_win32_carrier() {
4040
4129
  # which is exactly what property (1)'s `(_|$)` absorbs; all three shapes are fixtures
4041
4130
  # below so a future suffix change cannot pass vacuously.)
4042
4131
  pack_install_leaked_pi() {
4043
- grep '^@earendil-works+' | grep -Ev '@1\.4\.0(_|$)' || true
4132
+ grep '^@earendil-works+' | grep -Ev '@1\.5\.0(_|$)' || true
4044
4133
  }
4045
4134
 
4046
4135
  # Matcher self-test on SYNTHETIC lookalikes: a healthy install tree cannot exercise either
@@ -4057,18 +4146,18 @@ check_pack_pin_matcher() {
4057
4146
  # Cell 1 — the version boundary. Every measured peer-hash shape is a fixture: the
4058
4147
  # 0.84.4-era `_@modelcontextprotocol+sdk@…`, the `_ws@8.21.3` observed on a real
4059
4148
  # 0.85.1 tree, and `[측정 2026-09-20]` the combined suffix a 0.86.0 install carries.
4060
- # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.4.0-beta.1`,
4149
+ # None may leak; the two lookalikes must — a PREFIX-EXTENDED version (`1.5.0-beta.1`,
4061
4150
  # the prerelease shape that an unanchored match would bless) and an off-pin version.
4062
4151
  matcher_probe=$(printf '%s\n' \
4063
- '@earendil-works+pi-ai@1.4.0' \
4064
- '@earendil-works+pi-ai@1.4.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4065
- '@earendil-works+pi-ai@1.4.0_ws@8.21.3' \
4066
- '@earendil-works+pi-ai@1.4.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4067
- '@earendil-works+pi-ai@1.4.0-beta.1' \
4152
+ '@earendil-works+pi-ai@1.5.0' \
4153
+ '@earendil-works+pi-ai@1.5.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6' \
4154
+ '@earendil-works+pi-ai@1.5.0_ws@8.21.3' \
4155
+ '@earendil-works+pi-ai@1.5.0_@modelcontextprotocol+sdk@1.29.0_zod@4.3.6__ws@8.21.3_zod@4.3.6' \
4156
+ '@earendil-works+pi-ai@1.5.0-beta.1' \
4068
4157
  '@earendil-works+pi-agent-core@0.87.1' | pack_install_leaked_pi)
4069
- if [ "$matcher_probe" != '@earendil-works+pi-ai@1.4.0-beta.1
4158
+ if [ "$matcher_probe" != '@earendil-works+pi-ai@1.5.0-beta.1
4070
4159
  @earendil-works+pi-agent-core@0.87.1' ]; then
4071
- fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.4.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.4.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4160
+ fail "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED] the pin-leak matcher must flag the prefix-extended 1.5.0-beta.1 and the off-pin 0.87.1 lookalikes, and pass 1.5.0 bare or with any measured peer-hash — got: ${matcher_probe:-<nothing leaked>}"
4072
4161
  return 1
4073
4162
  fi
4074
4163
 
@@ -4076,9 +4165,9 @@ check_pack_pin_matcher() {
4076
4165
  # (0.85.0 onward, still true at 0.99.1). An off-pin chord MUST leak; the pinned one must not. A matcher narrowed
4077
4166
  # back to `^@earendil-works+pi-` sees nothing here and dies at this signature.
4078
4167
  matcher_probe=$(printf '%s\n' \
4079
- '@earendil-works+chord@1.4.0' \
4168
+ '@earendil-works+chord@1.5.0' \
4080
4169
  '@earendil-works+chord@0.87.1' \
4081
- '@earendil-works+pi-ai@1.4.0' | pack_install_leaked_pi)
4170
+ '@earendil-works+pi-ai@1.5.0' | pack_install_leaked_pi)
4082
4171
  if [ "$matcher_probe" != '@earendil-works+chord@0.87.1' ]; then
4083
4172
  fail "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE] the pin-leak matcher must cover every @earendil-works closure member, not just the pi-* families — an off-pin @earendil-works/chord has to leak (it was a runtime dependency of pi-coding-agent, pi-agent-core, pi-client and pi-protocol — `[측정 2026-09-20]` at 0.86.0, re-measured `[측정 2026-09-22]` at 0.87.0, `[측정 2026-09-23]` at 0.87.1 and `[측정 2026-09-30]` at 0.99.1, where pi-coding-agent adds pi-codemode and pi-mcp beside them; `[측정 2026-10-13]` at 1.2.0 pi-durable alone declares it) — got: ${matcher_probe:-<nothing leaked>}"
4084
4173
  return 1
@@ -4291,7 +4380,9 @@ ORACLE
4291
4380
  # SDK 1.4 pi-durable imports pi-ai's new `utils/identity`, absent from the older pi-ai control versions
4292
4381
  # checked here (1.0.3 and 1.3.0 both fail to load); pi-telemetry 1.0.3/1.3.0 and chord 1.3.0 lack exports
4293
4382
  # it imports. pi-codemode 1.3.0 loads, and depends on no SDK member, so this tree carries exactly one
4294
- # depth-2 mismatch (L4 keeps depth 3).
4383
+ # depth-2 mismatch (L4 keeps depth 3). `[측정 2026-11-03]` SDK 1.5.0 pi-durable still loads beside
4384
+ # pi-codemode 1.3.0 (registry tarball, integrity-matched) and 1.4.0 (#155 import probe; its codemode
4385
+ # imports are the root, /portable, /source and /declarations entries), so the control version holds.
4295
4386
  printf '%s\n' "{\"name\":\"pd-mismatch\",\"version\":\"1.0.0\",\"private\":true,\"overrides\":{\"@earendil-works/pi-durable\":{\"@earendil-works/pi-codemode\":\"1.3.0\"}}}" > "$box/pd-mismatch/package.json"
4296
4387
  out=$(cd "$box/pd-mismatch" && npm install "$tgz" --ignore-scripts --no-audit --no-fund 2>&1) || {
4297
4388
  fail "[check-pack-install] pi-durable L3 (mismatch control) install failed:"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
@@ -4421,7 +4512,13 @@ ORACLE
4421
4512
  local pin_minor="${pin_ver#*.}"; pin_minor="${pin_minor%%.*}"
4422
4513
  local pin_ceiling="${pin_ver%%.*}.$((pin_minor + 1))"
4423
4514
  mkdir -p "$host_old"
4424
- for spec in "${host_specs[@]}"; do host_old_specs+=("${spec%@*}@$host_old_ver"); done
4515
+ # The old-host fixture installs the members its own generation had. `[측정 2026-11-03]` pi-schema's
4516
+ # registry versions are 0.0.0-stage, 0.0.0 and 1.5.0 (#155: born with the 1.5 SDK), so it has no
4517
+ # release at the fixed old version — the only member skipped here; the pinned/aligned cells keep all ten.
4518
+ for spec in "${host_specs[@]}"; do
4519
+ [ "${spec%@*}" = '@earendil-works/pi-schema' ] && continue
4520
+ host_old_specs+=("${spec%@*}@$host_old_ver")
4521
+ done
4425
4522
  out=$(cd "$host_old" && npm install "${host_old_specs[@]}" --ignore-scripts --no-audit --no-fund 2>&1) || {
4426
4523
  fail "[check-pack-install] could not install the previous-host pi fixture ($host_old_ver):"; echo "$out" | tail -10 | sed 's/^/ /' >&2; return 1; }
4427
4524
  for cell in old aligned; do
@@ -4986,20 +5083,22 @@ _check_pack_install_impl() {
4986
5083
  # oracle walked 10 member edges with 0 mismatches (.tmp-verify/145-stage1/08-pack-install.log).
4987
5084
  # `[측정 2026-10-22]` at 1.4.0 the nine members are again exact 1.4.0 edges (registry manifests; #148 S0),
4988
5085
  # and pi-tui 1.4.0 adds one third-party dependency (marked-cjk-friendly 0.1.2) outside the closure.
4989
- echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.4.x SDK + chord + codemode + mcp + durable + typebox)"
5086
+ # `[측정 2026-11-03]` at 1.5.0 the set grows to ten: pi-schema 1.5.0 is new and declared by chord,
5087
+ # pi-agent-core, pi-ai and pi-durable; every member edge is exact 1.5.0 (registry manifests; #155).
5088
+ echo "[check-pack-install] pnpm add into $tmp (with the aligned 1.5.x SDK + chord + codemode + mcp + durable + schema)"
4990
5089
  local install_log
4991
5090
  install_log=$(cd "$tmp" && pnpm add \
4992
5091
  "$tgz_path" \
4993
- "@earendil-works/pi-ai@1.4.0" \
4994
- "@earendil-works/pi-coding-agent@1.4.0" \
4995
- "@earendil-works/pi-tui@1.4.0" \
4996
- "@earendil-works/pi-agent-core@1.4.0" \
4997
- "@earendil-works/pi-telemetry@1.4.0" \
4998
- "@earendil-works/chord@1.4.0" \
4999
- "@earendil-works/pi-codemode@1.4.0" \
5000
- "@earendil-works/pi-mcp@1.4.0" \
5001
- "@earendil-works/pi-durable@1.4.0" \
5002
- "typebox@latest" \
5092
+ "@earendil-works/pi-ai@1.5.0" \
5093
+ "@earendil-works/pi-coding-agent@1.5.0" \
5094
+ "@earendil-works/pi-tui@1.5.0" \
5095
+ "@earendil-works/pi-agent-core@1.5.0" \
5096
+ "@earendil-works/pi-telemetry@1.5.0" \
5097
+ "@earendil-works/chord@1.5.0" \
5098
+ "@earendil-works/pi-codemode@1.5.0" \
5099
+ "@earendil-works/pi-mcp@1.5.0" \
5100
+ "@earendil-works/pi-durable@1.5.0" \
5101
+ "@earendil-works/pi-schema@1.5.0" \
5003
5102
  --ignore-workspace --ignore-scripts 2>&1) || {
5004
5103
  fail "[check-pack-install] pnpm add failed:"
5005
5104
  echo "$install_log" | tail -10 | sed 's/^/ /' >&2
@@ -5014,11 +5113,11 @@ _check_pack_install_impl() {
5014
5113
  local leaked_pi
5015
5114
  leaked_pi=$(ls "$tmp/node_modules/.pnpm" 2>/dev/null | pack_install_leaked_pi)
5016
5115
  if [ -n "$leaked_pi" ]; then
5017
- fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.4.0):"
5116
+ fail "[check-pack-install] UNVERIFIED pi runtime resolved into the install tree (expected only 1.5.0):"
5018
5117
  printf '%s\n' "$leaked_pi" | sed 's/^/ /' >&2
5019
5118
  return 1
5020
5119
  fi
5021
- echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.4.0 (chord and pi-durable included)"
5120
+ echo "[check-pack-install] pi runtime tree pin verified: every @earendil-works package is 1.5.0 (chord, pi-durable and pi-schema included)"
5022
5121
 
5023
5122
  # Resolve the installed package.json and confirm pi.extensions
5024
5123
  # arrived intact. If pi.extensions is empty or missing, the
@@ -25,7 +25,7 @@
25
25
  * - R5: taxonomy covers the table reasons + pre-claims bad-target /
26
26
  * target-locked (so bucket B F2 won't reopen it), and
27
27
  * is PRE-DISPATCH only (no send-fail-fallback member).
28
- * - schema ↔ types drift guard: the TypeBox input/receipt property keysets and
28
+ * - schema ↔ types drift guard: the pi-schema input/receipt property keysets and
29
29
  * the StringEnum members match the hand-written contract constants.
30
30
  */
31
31
 
@@ -82,7 +82,7 @@ function eq(label: string, actual: unknown, expected: unknown): void {
82
82
  }
83
83
 
84
84
  // Pull the literal members out of a StringEnum / Optional(StringEnum) schema,
85
- // whichever shape typebox emits — so the schema↔const drift guard is real.
85
+ // whichever shape the schema builder emits — so the schema↔const drift guard is real.
86
86
  function enumValues(schema: unknown): string[] {
87
87
  const s = schema as { enum?: unknown[]; anyOf?: { const?: unknown }[] };
88
88
  if (Array.isArray(s?.enum)) return s.enum.map(String);
@@ -626,8 +626,8 @@ ok(
626
626
  !("action" in rej) && !("transport" in rej) && !("ownership" in rej),
627
627
  );
628
628
  eq("receipt.reject.reason enum === ENTWURF_V2_REJECT_REASONS", enumValues(rej.reason), [...ENTWURF_V2_REJECT_REASONS]);
629
- // ?6: reject.observedLiveness is a required-nullable union — Type.Union([StringEnum
630
- // (FACT_LIVENESSES), Type.Null()]) — NOT Optional and NOT a bare StringEnum. The
629
+ // ?6: reject.observedLiveness is a required-nullable union — union([StringEnum
630
+ // (FACT_LIVENESSES), nul()]) — NOT Optional and NOT a bare StringEnum. The
631
631
  // key is always present (required), the value may be a 4-value liveness OR null.
632
632
  const rejLiveness = rej.observedLiveness as { anyOf?: { type?: string }[] };
633
633
  eq("?6: receipt.reject.observedLiveness is a 2-branch union (liveness | null)", rejLiveness.anyOf?.length, 2);
@@ -3473,11 +3473,11 @@ let manifestCount: number;
3473
3473
  "copilot-birth": 19,
3474
3474
  "copilot-launch": 14,
3475
3475
  "pi-launch": 9,
3476
- "pi-floor": 3,
3476
+ "pi-floor": 4,
3477
3477
  "pi-bridge-sender": 4,
3478
3478
  "pi-mcp-bridge": 2,
3479
3479
  "pi-mcp-register": 3,
3480
- "pi-durable": 171,
3480
+ "pi-durable": 186,
3481
3481
  "copilot-receive": 20,
3482
3482
  "entwurf-peers": 1,
3483
3483
  "fresh-call-dispatch": 18,
@@ -194,7 +194,7 @@
194
194
  "claim": "PI-CORE-PIN-TRACKS-FLOOR",
195
195
  "title": "the queue oracle's pi-agent-core pin drifts off the floor the rest of the pi runtime is pinned to, so the oracle keeps answering honestly — about a pi nobody in this repo runs",
196
196
  "subject": "package.json",
197
- "find": ["\"@earendil-works/pi-agent-core\": \"1.4.0\","],
197
+ "find": ["\"@earendil-works/pi-agent-core\": \"1.5.0\","],
198
198
  "replace": ["\"@earendil-works/pi-agent-core\": \"0.87.1\","],
199
199
  "gate": ["bash", "run.sh", "check-dep-versions"],
200
200
  "timeoutSeconds": 300,
@@ -6,8 +6,8 @@
6
6
  "claim": "PACK-INSTALL-PIN-MATCHER-BOUNDED",
7
7
  "title": "the pin-leak matcher loses its version boundary and blesses substring lookalikes \u2014 @0.85.10 reads as the pinned 0.85.1 and the gate announces a verified tree it never verified (independent review, 2026-08-25)",
8
8
  "subject": "run.sh",
9
- "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.4\\.0(_|$)' || true"],
10
- "replace": [" grep '^@earendil-works+' | grep -v '@1\\.4\\.0' || true"],
9
+ "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
10
+ "replace": [" grep '^@earendil-works+' | grep -v '@1\\.5\\.0' || true"],
11
11
  "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
12
12
  "timeoutSeconds": 120,
13
13
  "signature": "[QK:PACK-INSTALL-PIN-MATCHER-BOUNDED]",
@@ -17,8 +17,8 @@
17
17
  "claim": "PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE",
18
18
  "title": "the pin-leak matcher narrows back to the pi-* families and goes blind to a non-pi member of the same runtime closure \u2014 @earendil-works/chord (new in pi 0.85.0, a runtime dependency of pi-coding-agent/pi-agent-core/pi-client/pi-protocol) floats unpinned while the gate prints a verified pin (measured 2026-09-06 on a real 0.85.1 install tree)",
19
19
  "subject": "run.sh",
20
- "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.4\\.0(_|$)' || true"],
21
- "replace": [" grep '^@earendil-works+pi-' | grep -Ev '@1\\.4\\.0(_|$)' || true"],
20
+ "find": [" grep '^@earendil-works+' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
21
+ "replace": [" grep '^@earendil-works+pi-' | grep -Ev '@1\\.5\\.0(_|$)' || true"],
22
22
  "gate": ["bash", "run.sh", "check-pack-pin-matcher"],
23
23
  "timeoutSeconds": 120,
24
24
  "signature": "[QK:PACK-INSTALL-PIN-MATCHER-COVERS-CLOSURE]",