@akagilnc/pi-workflow-roles 0.1.3669 → 0.1.3673

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.
@@ -4658,6 +4658,7 @@ async function collectBookRunTicketNumbers(input) {
4658
4658
  }
4659
4659
  const known = /* @__PURE__ */ new Set();
4660
4660
  for (const entry of entries) {
4661
+ if (!entry.includes("@")) continue;
4661
4662
  const ticketNumber = await readRunTicketNumber(join11(runsDir, entry));
4662
4663
  if (ticketNumber !== void 0) known.add(ticketNumber);
4663
4664
  }
@@ -9634,36 +9635,29 @@ var init_ticket_provenance = __esm({
9634
9635
  // src/public-cli/seat-ticket-binding.ts
9635
9636
  import { existsSync as existsSync6 } from "node:fs";
9636
9637
  function instructionTicketTokens(instruction) {
9637
- const tokens = /* @__PURE__ */ new Set();
9638
- for (const run of instruction.match(/\d+/g) ?? []) {
9639
- if (run.startsWith("0")) continue;
9640
- const parsed = Number(run);
9641
- if (!Number.isSafeInteger(parsed) || parsed < 1) continue;
9642
- tokens.add(parsed);
9638
+ const tokens = [];
9639
+ const seen = /* @__PURE__ */ new Set();
9640
+ for (const match of instruction.matchAll(/#([1-9]\d*)/g)) {
9641
+ const parsed = Number(match[1]);
9642
+ if (!Number.isSafeInteger(parsed)) continue;
9643
+ if (seen.has(parsed)) continue;
9644
+ seen.add(parsed);
9645
+ tokens.push(parsed);
9643
9646
  }
9644
- return [...tokens];
9647
+ return tokens;
9645
9648
  }
9646
9649
  async function resolveKnownTicketNumber(input) {
9647
9650
  const tokens = instructionTicketTokens(input.instruction);
9648
9651
  if (tokens.length === 0) return void 0;
9649
- const known = [];
9650
- let runTickets;
9651
- for (const token of tokens) {
9652
- const volume = resolveTicketProvenanceVolume(
9653
- token,
9654
- input.projectRoot,
9655
- input.home
9656
- );
9657
- if (existsSync6(volume.volumeDir)) {
9658
- known.push(token);
9659
- continue;
9660
- }
9661
- if (runTickets === void 0) {
9662
- runTickets = await readBookRunTickets(input);
9663
- }
9664
- if (runTickets.has(token)) known.push(token);
9665
- }
9666
- return known.length === 1 ? known[0] : void 0;
9652
+ const target = tokens[0];
9653
+ const volume = resolveTicketProvenanceVolume(
9654
+ target,
9655
+ input.projectRoot,
9656
+ input.home
9657
+ );
9658
+ if (existsSync6(volume.volumeDir)) return target;
9659
+ const runTickets = await readBookRunTickets(input);
9660
+ return runTickets.has(target) ? target : void 0;
9667
9661
  }
9668
9662
  async function readBookRunTickets(input) {
9669
9663
  let bookKey;
@@ -17831,6 +17831,7 @@ async function collectBookRunTicketNumbers(input) {
17831
17831
  }
17832
17832
  const known = /* @__PURE__ */ new Set();
17833
17833
  for (const entry of entries) {
17834
+ if (!entry.includes("@")) continue;
17834
17835
  const ticketNumber = await readRunTicketNumber(join11(runsDir, entry));
17835
17836
  if (ticketNumber !== void 0) known.add(ticketNumber);
17836
17837
  }
@@ -22240,42 +22241,33 @@ var init_ticket_provenance = __esm({
22240
22241
  // src/public-cli/seat-ticket-binding.ts
22241
22242
  import { existsSync as existsSync7 } from "node:fs";
22242
22243
  function instructionTicketTokens(instruction) {
22243
- const tokens = /* @__PURE__ */ new Set();
22244
- for (const run of instruction.match(/\d+/g) ?? []) {
22245
- if (run.startsWith("0")) continue;
22246
- const parsed = Number(run);
22247
- if (!Number.isSafeInteger(parsed) || parsed < 1) continue;
22248
- tokens.add(parsed);
22244
+ const tokens = [];
22245
+ const seen = /* @__PURE__ */ new Set();
22246
+ for (const match of instruction.matchAll(/#([1-9]\d*)/g)) {
22247
+ const parsed = Number(match[1]);
22248
+ if (!Number.isSafeInteger(parsed)) continue;
22249
+ if (seen.has(parsed)) continue;
22250
+ seen.add(parsed);
22251
+ tokens.push(parsed);
22249
22252
  }
22250
- return [...tokens];
22253
+ return tokens;
22251
22254
  }
22252
22255
  async function resolveKnownTicketNumber(input) {
22253
22256
  const tokens = instructionTicketTokens(input.instruction);
22254
22257
  if (tokens.length === 0) return void 0;
22255
- const known = [];
22256
- let runTickets;
22257
- for (const token of tokens) {
22258
- const volume = resolveTicketProvenanceVolume(
22259
- token,
22260
- input.projectRoot,
22261
- input.home
22262
- );
22263
- if (existsSync7(volume.volumeDir)) {
22264
- known.push(token);
22265
- continue;
22266
- }
22267
- if (runTickets === void 0) {
22268
- runTickets = await readBookRunTickets(input);
22269
- }
22270
- if (runTickets.has(token)) known.push(token);
22271
- }
22272
- return known.length === 1 ? known[0] : void 0;
22258
+ const target = tokens[0];
22259
+ const volume = resolveTicketProvenanceVolume(
22260
+ target,
22261
+ input.projectRoot,
22262
+ input.home
22263
+ );
22264
+ if (existsSync7(volume.volumeDir)) return target;
22265
+ const runTickets = await readBookRunTickets(input);
22266
+ return runTickets.has(target) ? target : void 0;
22273
22267
  }
22274
22268
  async function resolveDiaristSummonsTicketNumber(input) {
22275
- const known = await resolveKnownTicketNumber(input);
22276
- if (known !== void 0) return known;
22277
22269
  const tokens = instructionTicketTokens(input.instruction);
22278
- return tokens.length === 1 ? tokens[0] : void 0;
22270
+ return tokens[0];
22279
22271
  }
22280
22272
  async function readBookRunTickets(input) {
22281
22273
  let bookKey;
@@ -845,6 +845,10 @@ export async function collectBookRunTicketNumbers(input) {
845
845
  }
846
846
  const known = new Set();
847
847
  for (const entry of entries) {
848
+ // runs/ also holds non-run files (institutional-resolution.json, Finder
849
+ // .DS_Store); only `<runId>@<role>` directories are runs (#769).
850
+ if (!entry.includes("@"))
851
+ continue;
848
852
  const ticketNumber = await readRunTicketNumber(join(runsDir, entry));
849
853
  if (ticketNumber !== undefined)
850
854
  known.add(ticketNumber);
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747).
3
- * One path: unbound admission → reuse a ticket number this book already records
4
- * (retained run pages + 起居录 volumes) → bindAdmittedTicketNumber.
5
- * ADR 0081 `reuse-case-ticket-without-extra-llm`: no seat-side model call, no
6
- * second ticket-number source of truth, no minting a number from human titles.
7
- * Nothing matched is not 真无票 — the run simply stays unbound, which is lawful.
8
- * No CLI --ticket and no attachment frontmatter binding.
9
- * #747: officer same-parent resume also lives here (shared seam).
2
+ * Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747 / #771).
3
+ * Court target = first `#N` in the summons instruction. Other seats reuse that
4
+ * number only when this book already records it (retained run pages + 起居录
5
+ * volumes); 起居郎 takes it as the caller-supplied identity on a first summons
6
+ * (ADR 0081 `initial-court-ticket-supplied`). No seat-side model call, no second
7
+ * ticket-number source of truth, no minting from human titles. No `#N` leaves
8
+ * the run unbound (真无票), which is lawful. No CLI --ticket and no attachment
9
+ * frontmatter binding. #747: officer same-parent resume also lives here.
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
  import { ActivationGitRepositoryRequiredError, resolveBookKeyFromGit, } from "../activation-ledger-git.js";
@@ -14,61 +14,53 @@ import { resolveTicketProvenanceVolume } from "../ticket-provenance.js";
14
14
  import { bindAdmittedTicketNumber, } from "./invocation.js";
15
15
  import { collectBookRunTicketNumbers, findLatestRunIdForSeatTicket, } from "./run-lifecycle.js";
16
16
  /**
17
- * Complete decimal ticket tokens in an instruction.
18
- * A digit run is one token: `82` inside `#582` is not 82, and a leading zero
19
- * makes the run a different literal than the ticket number it would parse to.
17
+ * Hash-marked ticket references in an instruction, in order of first appearance.
18
+ * `#582` is one token; a leading-zero run is not a ticket literal; bare digit
19
+ * runs (`r1`, `D2`, sha islands, counts) are not ticket references. Callers name
20
+ * the court target as the first `#N` (ADR 0081 `initial-court-ticket-supplied`).
20
21
  */
21
22
  export function instructionTicketTokens(instruction) {
22
- const tokens = new Set();
23
- for (const run of instruction.match(/\d+/g) ?? []) {
24
- if (run.startsWith("0"))
23
+ const tokens = [];
24
+ const seen = new Set();
25
+ for (const match of instruction.matchAll(/#([1-9]\d*)/g)) {
26
+ const parsed = Number(match[1]);
27
+ if (!Number.isSafeInteger(parsed))
25
28
  continue;
26
- const parsed = Number(run);
27
- if (!Number.isSafeInteger(parsed) || parsed < 1)
29
+ if (seen.has(parsed))
28
30
  continue;
29
- tokens.add(parsed);
31
+ seen.add(parsed);
32
+ tokens.push(parsed);
30
33
  }
31
- return [...tokens];
34
+ return tokens;
32
35
  }
33
36
  /**
34
- * Ticket identity this summons reuses, or undefined when none is unambiguous.
35
- * Known identities come only from records this book already holds: 起居录 volume
36
- * partitions and ticket numbers on retained run pages. Zero or several known
37
- * tokens in the instruction leave the run unbound — never guess, never fail.
37
+ * Ticket identity this summons reuses, or undefined when the court target is not
38
+ * already on this book. The first `#N` is the court target; later `#N` are
39
+ * neighbors/PRs and never steal the bind. Known identities come only from
40
+ * records this book already holds: 起居录 volume partitions and ticket numbers
41
+ * on retained run pages. Nothing matched leaves the run unbound — never guess.
38
42
  */
39
43
  export async function resolveKnownTicketNumber(input) {
40
44
  const tokens = instructionTicketTokens(input.instruction);
41
45
  if (tokens.length === 0)
42
46
  return undefined;
43
- const known = [];
44
- let runTickets;
45
- for (const token of tokens) {
46
- const volume = resolveTicketProvenanceVolume(token, input.projectRoot, input.home);
47
- if (existsSync(volume.volumeDir)) {
48
- known.push(token);
49
- continue;
50
- }
51
- if (runTickets === undefined) {
52
- runTickets = await readBookRunTickets(input);
53
- }
54
- if (runTickets.has(token))
55
- known.push(token);
56
- }
57
- return known.length === 1 ? known[0] : undefined;
47
+ const target = tokens[0];
48
+ const volume = resolveTicketProvenanceVolume(target, input.projectRoot, input.home);
49
+ if (existsSync(volume.volumeDir))
50
+ return target;
51
+ const runTickets = await readBookRunTickets(input);
52
+ return runTickets.has(target) ? target : undefined;
58
53
  }
59
54
  /**
60
- * Diarist first-summons identity (ADR 0081 `initial-court-ticket-supplied`).
61
- * Prefer a book-known reuse; when the book has no record yet, the sole complete
62
- * decimal token in the caller's dispatch is the supplied ticket — the working
63
- * 起居郎 round establishes the volume. Several tokens or none stay unbound.
55
+ * Diarist summons identity (ADR 0081 `initial-court-ticket-supplied`).
56
+ * The first `#N` in the caller's dispatch is the supplied court target — the
57
+ * working 起居郎 round establishes the volume on a first summons; later `#N`
58
+ * (neighbors, PR numbers) do not unbind it. No `#N` stays unbound (真无票).
64
59
  * Not a second ticket-number source of truth and not an extra recognizer call.
65
60
  */
66
61
  export async function resolveDiaristSummonsTicketNumber(input) {
67
- const known = await resolveKnownTicketNumber(input);
68
- if (known !== undefined)
69
- return known;
70
62
  const tokens = instructionTicketTokens(input.instruction);
71
- return tokens.length === 1 ? tokens[0] : undefined;
63
+ return tokens[0];
72
64
  }
73
65
  /**
74
66
  * Ticket numbers on this book's retained runs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.3669",
3
+ "version": "0.1.3673",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -169,8 +169,8 @@ export async function runPublicDiarist(
169
169
  }
170
170
 
171
171
  // #637: same ticket → resume this seat's prior run with this summons' materials.
172
- // #709 / ADR 0081: reuse book records, or take the caller's sole dispatch token
173
- // on a first summons that has not yet minted a volume — no seat recognizer call.
172
+ // #709 / #771 / ADR 0081: first `#N` in the dispatch is the court target
173
+ // (neighbors/PRs/rN later in the prose do not unbind) — no seat recognizer call.
174
174
  const projectRoot = parsed.project ?? env.cwd;
175
175
  const reusedTicketNumber = await resolveDiaristSummonsTicketNumber({
176
176
  instruction: parsed.instruction,
@@ -1161,6 +1161,9 @@ export async function collectBookRunTicketNumbers(input: {
1161
1161
  }
1162
1162
  const known = new Set<number>();
1163
1163
  for (const entry of entries) {
1164
+ // runs/ also holds non-run files (institutional-resolution.json, Finder
1165
+ // .DS_Store); only `<runId>@<role>` directories are runs (#769).
1166
+ if (!entry.includes("@")) continue;
1164
1167
  const ticketNumber = await readRunTicketNumber(join(runsDir, entry));
1165
1168
  if (ticketNumber !== undefined) known.add(ticketNumber);
1166
1169
  }
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747).
3
- * One path: unbound admission → reuse a ticket number this book already records
4
- * (retained run pages + 起居录 volumes) → bindAdmittedTicketNumber.
5
- * ADR 0081 `reuse-case-ticket-without-extra-llm`: no seat-side model call, no
6
- * second ticket-number source of truth, no minting a number from human titles.
7
- * Nothing matched is not 真无票 — the run simply stays unbound, which is lawful.
8
- * No CLI --ticket and no attachment frontmatter binding.
9
- * #747: officer same-parent resume also lives here (shared seam).
2
+ * Shared ticket identity seam for public court seats (#635 / #637 / #709 / #747 / #771).
3
+ * Court target = first `#N` in the summons instruction. Other seats reuse that
4
+ * number only when this book already records it (retained run pages + 起居录
5
+ * volumes); 起居郎 takes it as the caller-supplied identity on a first summons
6
+ * (ADR 0081 `initial-court-ticket-supplied`). No seat-side model call, no second
7
+ * ticket-number source of truth, no minting from human titles. No `#N` leaves
8
+ * the run unbound (真无票), which is lawful. No CLI --ticket and no attachment
9
+ * frontmatter binding. #747: officer same-parent resume also lives here.
10
10
  */
11
11
  import { existsSync } from "node:fs";
12
12
 
@@ -31,28 +31,32 @@ export type SeatTicketBindingEnv = {
31
31
  };
32
32
 
33
33
  /**
34
- * Complete decimal ticket tokens in an instruction.
35
- * A digit run is one token: `82` inside `#582` is not 82, and a leading zero
36
- * makes the run a different literal than the ticket number it would parse to.
34
+ * Hash-marked ticket references in an instruction, in order of first appearance.
35
+ * `#582` is one token; a leading-zero run is not a ticket literal; bare digit
36
+ * runs (`r1`, `D2`, sha islands, counts) are not ticket references. Callers name
37
+ * the court target as the first `#N` (ADR 0081 `initial-court-ticket-supplied`).
37
38
  */
38
39
  export function instructionTicketTokens(
39
40
  instruction: string,
40
41
  ): readonly number[] {
41
- const tokens = new Set<number>();
42
- for (const run of instruction.match(/\d+/g) ?? []) {
43
- if (run.startsWith("0")) continue;
44
- const parsed = Number(run);
45
- if (!Number.isSafeInteger(parsed) || parsed < 1) continue;
46
- tokens.add(parsed);
42
+ const tokens: number[] = [];
43
+ const seen = new Set<number>();
44
+ for (const match of instruction.matchAll(/#([1-9]\d*)/g)) {
45
+ const parsed = Number(match[1]);
46
+ if (!Number.isSafeInteger(parsed)) continue;
47
+ if (seen.has(parsed)) continue;
48
+ seen.add(parsed);
49
+ tokens.push(parsed);
47
50
  }
48
- return [...tokens];
51
+ return tokens;
49
52
  }
50
53
 
51
54
  /**
52
- * Ticket identity this summons reuses, or undefined when none is unambiguous.
53
- * Known identities come only from records this book already holds: 起居录 volume
54
- * partitions and ticket numbers on retained run pages. Zero or several known
55
- * tokens in the instruction leave the run unbound — never guess, never fail.
55
+ * Ticket identity this summons reuses, or undefined when the court target is not
56
+ * already on this book. The first `#N` is the court target; later `#N` are
57
+ * neighbors/PRs and never steal the bind. Known identities come only from
58
+ * records this book already holds: 起居录 volume partitions and ticket numbers
59
+ * on retained run pages. Nothing matched leaves the run unbound — never guess.
56
60
  */
57
61
  export async function resolveKnownTicketNumber(input: {
58
62
  readonly instruction: string;
@@ -62,31 +66,22 @@ export async function resolveKnownTicketNumber(input: {
62
66
  }): Promise<number | undefined> {
63
67
  const tokens = instructionTicketTokens(input.instruction);
64
68
  if (tokens.length === 0) return undefined;
65
- const known: number[] = [];
66
- let runTickets: ReadonlySet<number> | undefined;
67
- for (const token of tokens) {
68
- const volume = resolveTicketProvenanceVolume(
69
- token,
70
- input.projectRoot,
71
- input.home,
72
- );
73
- if (existsSync(volume.volumeDir)) {
74
- known.push(token);
75
- continue;
76
- }
77
- if (runTickets === undefined) {
78
- runTickets = await readBookRunTickets(input);
79
- }
80
- if (runTickets.has(token)) known.push(token);
81
- }
82
- return known.length === 1 ? known[0] : undefined;
69
+ const target = tokens[0]!;
70
+ const volume = resolveTicketProvenanceVolume(
71
+ target,
72
+ input.projectRoot,
73
+ input.home,
74
+ );
75
+ if (existsSync(volume.volumeDir)) return target;
76
+ const runTickets = await readBookRunTickets(input);
77
+ return runTickets.has(target) ? target : undefined;
83
78
  }
84
79
 
85
80
  /**
86
- * Diarist first-summons identity (ADR 0081 `initial-court-ticket-supplied`).
87
- * Prefer a book-known reuse; when the book has no record yet, the sole complete
88
- * decimal token in the caller's dispatch is the supplied ticket — the working
89
- * 起居郎 round establishes the volume. Several tokens or none stay unbound.
81
+ * Diarist summons identity (ADR 0081 `initial-court-ticket-supplied`).
82
+ * The first `#N` in the caller's dispatch is the supplied court target — the
83
+ * working 起居郎 round establishes the volume on a first summons; later `#N`
84
+ * (neighbors, PR numbers) do not unbind it. No `#N` stays unbound (真无票).
90
85
  * Not a second ticket-number source of truth and not an extra recognizer call.
91
86
  */
92
87
  export async function resolveDiaristSummonsTicketNumber(input: {
@@ -95,10 +90,8 @@ export async function resolveDiaristSummonsTicketNumber(input: {
95
90
  readonly home: string;
96
91
  readonly bookKey?: string;
97
92
  }): Promise<number | undefined> {
98
- const known = await resolveKnownTicketNumber(input);
99
- if (known !== undefined) return known;
100
93
  const tokens = instructionTicketTokens(input.instruction);
101
- return tokens.length === 1 ? tokens[0] : undefined;
94
+ return tokens[0];
102
95
  }
103
96
 
104
97
  /**