@akagilnc/pi-workflow-roles 0.1.3671 → 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.
@@ -9635,36 +9635,29 @@ var init_ticket_provenance = __esm({
9635
9635
  // src/public-cli/seat-ticket-binding.ts
9636
9636
  import { existsSync as existsSync6 } from "node:fs";
9637
9637
  function instructionTicketTokens(instruction) {
9638
- const tokens = /* @__PURE__ */ new Set();
9639
- for (const run of instruction.match(/\d+/g) ?? []) {
9640
- if (run.startsWith("0")) continue;
9641
- const parsed = Number(run);
9642
- if (!Number.isSafeInteger(parsed) || parsed < 1) continue;
9643
- 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);
9644
9646
  }
9645
- return [...tokens];
9647
+ return tokens;
9646
9648
  }
9647
9649
  async function resolveKnownTicketNumber(input) {
9648
9650
  const tokens = instructionTicketTokens(input.instruction);
9649
9651
  if (tokens.length === 0) return void 0;
9650
- const known = [];
9651
- let runTickets;
9652
- for (const token of tokens) {
9653
- const volume = resolveTicketProvenanceVolume(
9654
- token,
9655
- input.projectRoot,
9656
- input.home
9657
- );
9658
- if (existsSync6(volume.volumeDir)) {
9659
- known.push(token);
9660
- continue;
9661
- }
9662
- if (runTickets === void 0) {
9663
- runTickets = await readBookRunTickets(input);
9664
- }
9665
- if (runTickets.has(token)) known.push(token);
9666
- }
9667
- 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;
9668
9661
  }
9669
9662
  async function readBookRunTickets(input) {
9670
9663
  let bookKey;
@@ -22241,42 +22241,33 @@ var init_ticket_provenance = __esm({
22241
22241
  // src/public-cli/seat-ticket-binding.ts
22242
22242
  import { existsSync as existsSync7 } from "node:fs";
22243
22243
  function instructionTicketTokens(instruction) {
22244
- const tokens = /* @__PURE__ */ new Set();
22245
- for (const run of instruction.match(/\d+/g) ?? []) {
22246
- if (run.startsWith("0")) continue;
22247
- const parsed = Number(run);
22248
- if (!Number.isSafeInteger(parsed) || parsed < 1) continue;
22249
- 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);
22250
22252
  }
22251
- return [...tokens];
22253
+ return tokens;
22252
22254
  }
22253
22255
  async function resolveKnownTicketNumber(input) {
22254
22256
  const tokens = instructionTicketTokens(input.instruction);
22255
22257
  if (tokens.length === 0) return void 0;
22256
- const known = [];
22257
- let runTickets;
22258
- for (const token of tokens) {
22259
- const volume = resolveTicketProvenanceVolume(
22260
- token,
22261
- input.projectRoot,
22262
- input.home
22263
- );
22264
- if (existsSync7(volume.volumeDir)) {
22265
- known.push(token);
22266
- continue;
22267
- }
22268
- if (runTickets === void 0) {
22269
- runTickets = await readBookRunTickets(input);
22270
- }
22271
- if (runTickets.has(token)) known.push(token);
22272
- }
22273
- 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;
22274
22267
  }
22275
22268
  async function resolveDiaristSummonsTicketNumber(input) {
22276
- const known = await resolveKnownTicketNumber(input);
22277
- if (known !== void 0) return known;
22278
22269
  const tokens = instructionTicketTokens(input.instruction);
22279
- return tokens.length === 1 ? tokens[0] : void 0;
22270
+ return tokens[0];
22280
22271
  }
22281
22272
  async function readBookRunTickets(input) {
22282
22273
  let bookKey;
@@ -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.3671",
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,
@@ -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
  /**