@akagilnc/pi-workflow-roles 0.1.5139 → 0.1.5146

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.
@@ -962,6 +962,7 @@ __export(run_ticket_number_exports, {
962
962
  MIGRATION_TICKET_DERIVATION_PAGE: () => MIGRATION_TICKET_DERIVATION_PAGE,
963
963
  isSafePositiveTicketNumber: () => isSafePositiveTicketNumber,
964
964
  readBoardTicketNumber: () => readBoardTicketNumber,
965
+ readDeclaredTicketNumber: () => readDeclaredTicketNumber,
965
966
  readMigrationDerivedTicketNumber: () => readMigrationDerivedTicketNumber,
966
967
  readRunTicketNumber: () => readRunTicketNumber,
967
968
  requireSafePositiveTicketNumber: () => requireSafePositiveTicketNumber
@@ -974,6 +975,15 @@ function isEnoent(error) {
974
975
  function isSafePositiveTicketNumber(value) {
975
976
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
976
977
  }
978
+ function readDeclaredTicketNumber(value) {
979
+ if (isSafePositiveTicketNumber(value)) return value;
980
+ if (typeof value !== "string") return void 0;
981
+ const trimmed = value.trim();
982
+ const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
983
+ if (match === null) return void 0;
984
+ const ticketNumber = Number(match[1]);
985
+ return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
986
+ }
977
987
  function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
978
988
  if (!isSafePositiveTicketNumber(ticketNumber)) {
979
989
  throw new Error(
@@ -3173,7 +3183,7 @@ function withTerminatingOutputDeclarations(schema) {
3173
3183
  ...baseProperties ?? {},
3174
3184
  ...baseProperties?.ticketNumber === void 0 ? {
3175
3185
  ticketNumber: Type3.Unknown({
3176
- description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\u3002"
3186
+ description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\uFF08\u6B63\u6574\u6570\u3001\u6570\u5B57\u4E32\u6216\u524D\u5BFC #N\uFF1B\u5F52\u4F4D\u53EA\u8BFB\u672C\u5B57\u6BB5\uFF09\u3002"
3177
3187
  })
3178
3188
  } : {},
3179
3189
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
@@ -17658,12 +17668,18 @@ async function dispatchPostAdmissionTurn(input) {
17658
17668
  admitted.runId,
17659
17669
  { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join33(admitted.runDirectory, "session", "session.jsonl") }
17660
17670
  );
17661
- const asserted = rows.filter((row) => row.kind === "accepted" && row.role === admitted.role).map((row) => row.accepted).find((payload) => {
17662
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
17663
- const value = payload.ticketNumber;
17664
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
17665
- });
17666
- const ticketNumber = asserted === void 0 ? void 0 : asserted.ticketNumber;
17671
+ let ticketNumber;
17672
+ for (const row of rows) {
17673
+ if (row.role !== admitted.role || row.kind !== "accepted" && row.kind !== "audit-escalation") {
17674
+ continue;
17675
+ }
17676
+ const payload = row.accepted;
17677
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) continue;
17678
+ ticketNumber = readDeclaredTicketNumber(
17679
+ payload.ticketNumber
17680
+ );
17681
+ if (ticketNumber !== void 0) break;
17682
+ }
17667
17683
  if (ticketNumber !== void 0) await bindAdmittedTicketNumber(admitted, ticketNumber);
17668
17684
  }
17669
17685
  const relocation = await relocateAdmittedRunToTicket(admitted, env.principalAuthority, lease);
@@ -18482,6 +18498,7 @@ var init_post_admission = __esm({
18482
18498
  init_cli_errors();
18483
18499
  init_invocation();
18484
18500
  init_submission_ledger();
18501
+ init_run_ticket_number();
18485
18502
  init_activation_ledger_topology();
18486
18503
  init_engine_material();
18487
18504
  init_session_identity();
@@ -969,6 +969,7 @@ __export(run_ticket_number_exports, {
969
969
  MIGRATION_TICKET_DERIVATION_PAGE: () => MIGRATION_TICKET_DERIVATION_PAGE,
970
970
  isSafePositiveTicketNumber: () => isSafePositiveTicketNumber,
971
971
  readBoardTicketNumber: () => readBoardTicketNumber,
972
+ readDeclaredTicketNumber: () => readDeclaredTicketNumber,
972
973
  readMigrationDerivedTicketNumber: () => readMigrationDerivedTicketNumber,
973
974
  readRunTicketNumber: () => readRunTicketNumber,
974
975
  requireSafePositiveTicketNumber: () => requireSafePositiveTicketNumber
@@ -981,6 +982,15 @@ function isEnoent(error) {
981
982
  function isSafePositiveTicketNumber(value) {
982
983
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
983
984
  }
985
+ function readDeclaredTicketNumber(value) {
986
+ if (isSafePositiveTicketNumber(value)) return value;
987
+ if (typeof value !== "string") return void 0;
988
+ const trimmed = value.trim();
989
+ const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
990
+ if (match === null) return void 0;
991
+ const ticketNumber = Number(match[1]);
992
+ return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
993
+ }
984
994
  function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
985
995
  if (!isSafePositiveTicketNumber(ticketNumber)) {
986
996
  throw new Error(
@@ -2507,7 +2517,7 @@ function withTerminatingOutputDeclarations(schema) {
2507
2517
  ...baseProperties ?? {},
2508
2518
  ...baseProperties?.ticketNumber === void 0 ? {
2509
2519
  ticketNumber: Type3.Unknown({
2510
- description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\u3002"
2520
+ description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\uFF08\u6B63\u6574\u6570\u3001\u6570\u5B57\u4E32\u6216\u524D\u5BFC #N\uFF1B\u5F52\u4F4D\u53EA\u8BFB\u672C\u5B57\u6BB5\uFF09\u3002"
2511
2521
  })
2512
2522
  } : {},
2513
2523
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
@@ -17119,12 +17129,18 @@ async function dispatchPostAdmissionTurn(input) {
17119
17129
  admitted.runId,
17120
17130
  { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join31(admitted.runDirectory, "session", "session.jsonl") }
17121
17131
  );
17122
- const asserted = rows.filter((row) => row.kind === "accepted" && row.role === admitted.role).map((row) => row.accepted).find((payload) => {
17123
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
17124
- const value = payload.ticketNumber;
17125
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
17126
- });
17127
- const ticketNumber = asserted === void 0 ? void 0 : asserted.ticketNumber;
17132
+ let ticketNumber;
17133
+ for (const row of rows) {
17134
+ if (row.role !== admitted.role || row.kind !== "accepted" && row.kind !== "audit-escalation") {
17135
+ continue;
17136
+ }
17137
+ const payload = row.accepted;
17138
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) continue;
17139
+ ticketNumber = readDeclaredTicketNumber(
17140
+ payload.ticketNumber
17141
+ );
17142
+ if (ticketNumber !== void 0) break;
17143
+ }
17128
17144
  if (ticketNumber !== void 0) await bindAdmittedTicketNumber(admitted, ticketNumber);
17129
17145
  }
17130
17146
  const relocation = await relocateAdmittedRunToTicket(admitted, env.principalAuthority, lease);
@@ -17943,6 +17959,7 @@ var init_post_admission = __esm({
17943
17959
  init_cli_errors();
17944
17960
  init_invocation();
17945
17961
  init_submission_ledger();
17962
+ init_run_ticket_number();
17946
17963
  init_activation_ledger_topology();
17947
17964
  init_engine_material();
17948
17965
  init_session_identity();
@@ -8199,7 +8199,7 @@ function withTerminatingOutputDeclarations(schema) {
8199
8199
  ...baseProperties ?? {},
8200
8200
  ...baseProperties?.ticketNumber === void 0 ? {
8201
8201
  ticketNumber: typebox_exports.Unknown({
8202
- description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\u3002"
8202
+ description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\uFF08\u6B63\u6574\u6570\u3001\u6570\u5B57\u4E32\u6216\u524D\u5BFC #N\uFF1B\u5F52\u4F4D\u53EA\u8BFB\u672C\u5B57\u6BB5\uFF09\u3002"
8203
8203
  })
8204
8204
  } : {},
8205
8205
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
@@ -25,7 +25,7 @@ function withTerminatingOutputDeclarations(schema) {
25
25
  ...baseProperties ?? {},
26
26
  ...baseProperties?.ticketNumber === void 0 ? {
27
27
  ticketNumber: Type.Unknown({
28
- description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\u3002"
28
+ description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\uFF08\u6B63\u6574\u6570\u3001\u6570\u5B57\u4E32\u6216\u524D\u5BFC #N\uFF1B\u5F52\u4F4D\u53EA\u8BFB\u672C\u5B57\u6BB5\uFF09\u3002"
29
29
  })
30
30
  } : {},
31
31
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
@@ -379,6 +379,7 @@ __export(run_ticket_number_exports, {
379
379
  MIGRATION_TICKET_DERIVATION_PAGE: () => MIGRATION_TICKET_DERIVATION_PAGE,
380
380
  isSafePositiveTicketNumber: () => isSafePositiveTicketNumber,
381
381
  readBoardTicketNumber: () => readBoardTicketNumber,
382
+ readDeclaredTicketNumber: () => readDeclaredTicketNumber,
382
383
  readMigrationDerivedTicketNumber: () => readMigrationDerivedTicketNumber,
383
384
  readRunTicketNumber: () => readRunTicketNumber,
384
385
  requireSafePositiveTicketNumber: () => requireSafePositiveTicketNumber
@@ -391,6 +392,15 @@ function isEnoent(error) {
391
392
  function isSafePositiveTicketNumber(value) {
392
393
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
393
394
  }
395
+ function readDeclaredTicketNumber(value) {
396
+ if (isSafePositiveTicketNumber(value)) return value;
397
+ if (typeof value !== "string") return void 0;
398
+ const trimmed = value.trim();
399
+ const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
400
+ if (match === null) return void 0;
401
+ const ticketNumber = Number(match[1]);
402
+ return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
403
+ }
394
404
  function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
395
405
  if (!isSafePositiveTicketNumber(ticketNumber)) {
396
406
  throw new Error(
@@ -8798,7 +8808,7 @@ function withTerminatingOutputDeclarations(schema) {
8798
8808
  ...baseProperties ?? {},
8799
8809
  ...baseProperties?.ticketNumber === void 0 ? {
8800
8810
  ticketNumber: typebox_exports.Unknown({
8801
- description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\u3002"
8811
+ description: "\u53EF\u9009\u672C\u7968\u53F7\u3002\u5C1A\u672A\u7ED1\u5B9A\u65F6\u7531\u89D2\u8272\u5728\u65E2\u6709\u56DE\u6267\u4E2D\u7533\u62A5\uFF08\u6B63\u6574\u6570\u3001\u6570\u5B57\u4E32\u6216\u524D\u5BFC #N\uFF1B\u5F52\u4F4D\u53EA\u8BFB\u672C\u5B57\u6BB5\uFF09\u3002"
8802
8812
  })
8803
8813
  } : {},
8804
8814
  [INFRASTRUCTURE_FAILURE_DECLARATION_KEY]: infrastructureFailureDeclarationSchema.properties[INFRASTRUCTURE_FAILURE_DECLARATION_KEY]
@@ -26697,12 +26707,18 @@ async function dispatchPostAdmissionTurn(input) {
26697
26707
  admitted.runId,
26698
26708
  { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join27(admitted.runDirectory, "session", "session.jsonl") }
26699
26709
  );
26700
- const asserted = rows.filter((row) => row.kind === "accepted" && row.role === admitted.role).map((row) => row.accepted).find((payload) => {
26701
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
26702
- const value = payload.ticketNumber;
26703
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
26704
- });
26705
- const ticketNumber = asserted === void 0 ? void 0 : asserted.ticketNumber;
26710
+ let ticketNumber;
26711
+ for (const row of rows) {
26712
+ if (row.role !== admitted.role || row.kind !== "accepted" && row.kind !== "audit-escalation") {
26713
+ continue;
26714
+ }
26715
+ const payload = row.accepted;
26716
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) continue;
26717
+ ticketNumber = readDeclaredTicketNumber(
26718
+ payload.ticketNumber
26719
+ );
26720
+ if (ticketNumber !== void 0) break;
26721
+ }
26706
26722
  if (ticketNumber !== void 0) await bindAdmittedTicketNumber(admitted, ticketNumber);
26707
26723
  }
26708
26724
  const relocation = await relocateAdmittedRunToTicket(admitted, env.principalAuthority, lease);
@@ -27521,6 +27537,7 @@ var init_post_admission = __esm({
27521
27537
  init_cli_errors();
27522
27538
  init_invocation();
27523
27539
  init_submission_ledger();
27540
+ init_run_ticket_number();
27524
27541
  init_activation_ledger_topology();
27525
27542
  init_engine_material();
27526
27543
  init_session_identity();
@@ -12,6 +12,7 @@ import { buildAutoResumeContinuationPrompt, findRunDirectoryById, readRoleRunSta
12
12
  import { CliUsageError } from "./cli-errors.js";
13
13
  import { bindAdmittedTicketNumber, buildInstructionTransportPrompt, freezeAttachmentsIntoRun, relocateAdmittedRunToTicket, } from "./invocation.js";
14
14
  import { readRecordedSubmissionRows } from "../submission-ledger.js";
15
+ import { readDeclaredTicketNumber } from "../run-ticket-number.js";
15
16
  import { pathContainedIn } from "../activation-ledger-topology.js";
16
17
  import { pickEngineAxis } from "../package-resources/engine-material.js";
17
18
  import { readStoredHostSessionId, resolveHostAwareSessionAvailability, } from "../session-identity.js";
@@ -347,22 +348,27 @@ export async function dispatchPostAdmissionTurn(input) {
347
348
  };
348
349
  }
349
350
  try {
350
- // #858: the first typed assertion files an unbound run. Later receipts
351
- // remain untouched; this seam does not adjudicate a ticket change.
351
+ // #858 / #1071: the first identifiable ticketNumber field declaration
352
+ // files an unbound run (integer, digit string, or leading #N token).
353
+ // Terminal rows match settlement (#881): accepted and audit-escalation —
354
+ // an escalated submission still carries the original role params.
355
+ // Later receipts remain untouched; prose note/report is never consulted;
356
+ // this seam does not adjudicate a ticket change.
352
357
  if (admitted.ticketNumber === undefined) {
353
358
  const rows = await readRecordedSubmissionRows(admitted.projectRoot, admitted.runId, { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join(admitted.runDirectory, "session", "session.jsonl") });
354
- const asserted = rows
355
- .filter((row) => row.kind === "accepted" && row.role === admitted.role)
356
- .map((row) => row.accepted)
357
- .find((payload) => {
359
+ let ticketNumber;
360
+ for (const row of rows) {
361
+ if (row.role !== admitted.role
362
+ || (row.kind !== "accepted" && row.kind !== "audit-escalation")) {
363
+ continue;
364
+ }
365
+ const payload = row.accepted;
358
366
  if (payload === null || typeof payload !== "object" || Array.isArray(payload))
359
- return false;
360
- const value = payload.ticketNumber;
361
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
362
- });
363
- const ticketNumber = asserted === undefined
364
- ? undefined
365
- : asserted.ticketNumber;
367
+ continue;
368
+ ticketNumber = readDeclaredTicketNumber(payload.ticketNumber);
369
+ if (ticketNumber !== undefined)
370
+ break;
371
+ }
366
372
  if (ticketNumber !== undefined)
367
373
  await bindAdmittedTicketNumber(admitted, ticketNumber);
368
374
  }
@@ -7,6 +7,15 @@ function isEnoent(error) {
7
7
  function isSafePositiveTicketNumber(value) {
8
8
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
9
9
  }
10
+ function readDeclaredTicketNumber(value) {
11
+ if (isSafePositiveTicketNumber(value)) return value;
12
+ if (typeof value !== "string") return void 0;
13
+ const trimmed = value.trim();
14
+ const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
15
+ if (match === null) return void 0;
16
+ const ticketNumber = Number(match[1]);
17
+ return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : void 0;
18
+ }
10
19
  function requireSafePositiveTicketNumber(ticketNumber, context = "ticketNumber") {
11
20
  if (!isSafePositiveTicketNumber(ticketNumber)) {
12
21
  throw new Error(
@@ -53,6 +62,7 @@ export {
53
62
  MIGRATION_TICKET_DERIVATION_PAGE,
54
63
  isSafePositiveTicketNumber,
55
64
  readBoardTicketNumber,
65
+ readDeclaredTicketNumber,
56
66
  readMigrationDerivedTicketNumber,
57
67
  readRunTicketNumber,
58
68
  requireSafePositiveTicketNumber
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akagilnc/pi-workflow-roles",
3
- "version": "0.1.5139",
3
+ "version": "0.1.5146",
4
4
  "description": "Soul-bound workflow roles for Pi",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -65,7 +65,8 @@ export function withTerminatingOutputDeclarations<
65
65
  baseProperties?.ticketNumber === undefined
66
66
  ? {
67
67
  ticketNumber: Type.Unknown({
68
- description: "可选本票号。尚未绑定时由角色在既有回执中申报。",
68
+ description:
69
+ "可选本票号。尚未绑定时由角色在既有回执中申报(正整数、数字串或前导 #N;归位只读本字段)。",
69
70
  }),
70
71
  }
71
72
  : {}
@@ -26,6 +26,7 @@ import {
26
26
  relocateAdmittedRunToTicket,
27
27
  } from "./invocation.ts";
28
28
  import { readRecordedSubmissionRows } from "../submission-ledger.ts";
29
+ import { readDeclaredTicketNumber } from "../run-ticket-number.ts";
29
30
  import { pathContainedIn } from "../activation-ledger-topology.ts";
30
31
  import { pickEngineAxis } from "../package-resources/engine-material.ts";
31
32
  import {
@@ -732,25 +733,33 @@ export async function dispatchPostAdmissionTurn<
732
733
  };
733
734
  }
734
735
  try {
735
- // #858: the first typed assertion files an unbound run. Later receipts
736
- // remain untouched; this seam does not adjudicate a ticket change.
736
+ // #858 / #1071: the first identifiable ticketNumber field declaration
737
+ // files an unbound run (integer, digit string, or leading #N token).
738
+ // Terminal rows match settlement (#881): accepted and audit-escalation —
739
+ // an escalated submission still carries the original role params.
740
+ // Later receipts remain untouched; prose note/report is never consulted;
741
+ // this seam does not adjudicate a ticket change.
737
742
  if (admitted.ticketNumber === undefined) {
738
743
  const rows = await readRecordedSubmissionRows(
739
744
  admitted.projectRoot,
740
745
  admitted.runId,
741
746
  { home: homeFromRunDirectory(admitted.runDirectory), sessionParent: join(admitted.runDirectory, "session", "session.jsonl") },
742
747
  );
743
- const asserted = rows
744
- .filter((row) => row.kind === "accepted" && row.role === admitted.role)
745
- .map((row) => row.accepted)
746
- .find((payload) => {
747
- if (payload === null || typeof payload !== "object" || Array.isArray(payload)) return false;
748
- const value = (payload as { ticketNumber?: unknown }).ticketNumber;
749
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0;
750
- });
751
- const ticketNumber = asserted === undefined
752
- ? undefined
753
- : (asserted as { ticketNumber: number }).ticketNumber;
748
+ let ticketNumber: number | undefined;
749
+ for (const row of rows) {
750
+ if (
751
+ row.role !== admitted.role
752
+ || (row.kind !== "accepted" && row.kind !== "audit-escalation")
753
+ ) {
754
+ continue;
755
+ }
756
+ const payload = row.accepted;
757
+ if (payload === null || typeof payload !== "object" || Array.isArray(payload)) continue;
758
+ ticketNumber = readDeclaredTicketNumber(
759
+ (payload as { ticketNumber?: unknown }).ticketNumber,
760
+ );
761
+ if (ticketNumber !== undefined) break;
762
+ }
754
763
  if (ticketNumber !== undefined) await bindAdmittedTicketNumber(admitted, ticketNumber);
755
764
  }
756
765
  // #863: shared post-admission bind must relocate unbound→ticket in-home
@@ -25,6 +25,26 @@ export function isSafePositiveTicketNumber(value: unknown): value is number {
25
25
  return typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
26
26
  }
27
27
 
28
+ /**
29
+ * #1071 — read a role-declared ticketNumber field value for post-admission bind.
30
+ * Accepts a safe positive integer, a digit string, or a leading `#N` token
31
+ * (optional trailing material on the same field, e.g. `#1843 / PR #1876`).
32
+ * Decimal-looking prefixes (`1843.5`, `#1843.5`) are unidentifiable → undefined
33
+ * (leave unbound). Never reads prose note/report fields — callers must pass the
34
+ * typed ticketNumber value only.
35
+ */
36
+ export function readDeclaredTicketNumber(value: unknown): number | undefined {
37
+ if (isSafePositiveTicketNumber(value)) return value;
38
+ if (typeof value !== "string") return undefined;
39
+ const trimmed = value.trim();
40
+ // `(?!\.\d)` keeps `#1843 / PR #1876` while rejecting `#1843.5` / `1843.5`
41
+ // (word-boundary alone would truncate at the decimal point).
42
+ const match = /^#?([1-9]\d*)(?!\.\d)(?:\b|$)/.exec(trimmed);
43
+ if (match === null) return undefined;
44
+ const ticketNumber = Number(match[1]);
45
+ return isSafePositiveTicketNumber(ticketNumber) ? ticketNumber : undefined;
46
+ }
47
+
28
48
  /** Loud reject for non-safe-positive ticket numbers before placement or bind. */
29
49
  export function requireSafePositiveTicketNumber(
30
50
  ticketNumber: number,