@gitroll/tacitt-cli 0.6.12 → 0.6.14

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.
Files changed (46) hide show
  1. package/dist/{agent-setup-B12VbvN_.mjs → agent-setup-CEPQfjBv.mjs} +2 -2
  2. package/dist/agent-setup-worker.mjs +5 -5
  3. package/dist/cli.mjs +10 -10
  4. package/dist/codex-title-worker.mjs +1 -1
  5. package/dist/configured-preview-worker.mjs +5 -5
  6. package/dist/configured-session-worker.mjs +5 -5
  7. package/dist/{frame-B3He21G8.mjs → frame-CRm99088.mjs} +1 -1
  8. package/dist/{heap-Blrat--4.mjs → heap-Bh9tySJp.mjs} +12 -7
  9. package/dist/inspection-worker.mjs +4 -4
  10. package/dist/{job-CBuIMTQ0.mjs → job-CsWKWcM4.mjs} +135 -36
  11. package/dist/{locate-D-ZaHpI0.mjs → locate-CP2t26j2.mjs} +449 -244
  12. package/dist/{model-DR9odhWP.mjs → model-ChHuVKoB.mjs} +1 -1
  13. package/dist/operation-executor.mjs +3 -3
  14. package/dist/preview-worker.mjs +1 -1
  15. package/dist/privacy-validator.mjs +1 -1
  16. package/dist/{query-BYDcNKy2.mjs → query-D28k3XOr.mjs} +6044 -5706
  17. package/dist/{run-DQ9xyKSt.mjs → run-CiJmV__5.mjs} +6 -6
  18. package/dist/{src-Do-qzuMn.mjs → src-B3il9x5F.mjs} +617 -545
  19. package/dist/{src-CKQlTA2Z.mjs → src-CWMrGXJK.mjs} +180 -30
  20. package/dist/{src-DeNJ8vOD.mjs → src-CmvEJj46.mjs} +1806 -1690
  21. package/dist/store-database-worker.mjs +1 -1
  22. package/dist/web/assets/index-XdWDOC5H.js +172 -0
  23. package/dist/web/index.html +1 -1
  24. package/package.json +1 -1
  25. package/skills/tacitt/INDEX.md +1 -1
  26. package/skills/tacitt/SKILL.md +5 -3
  27. package/skills/tacitt/references/INDEX.md +2 -2
  28. package/skills/tacitt/references/b-method.md +4 -2
  29. package/skills/tacitt/references/bootstrap.md +1 -1
  30. package/skills/tacitt/references/commands.md +23 -4
  31. package/skills/tacitt/references/journey.md +5 -3
  32. package/skills/tacitt/references/parallel-review.md +22 -9
  33. package/skills/tacitt/references/remote-workers.md +6 -2
  34. package/skills/tacitt/scripts/INDEX.md +5 -5
  35. package/skills/tacitt/scripts/answer.mjs +12 -1
  36. package/skills/tacitt/scripts/describe.mjs +3 -2
  37. package/skills/tacitt/scripts/journey-classification-tasks.mjs +2 -0
  38. package/skills/tacitt/scripts/journey-classification.mjs +1 -0
  39. package/skills/tacitt/scripts/journey-scan-timing.mjs +3 -2
  40. package/skills/tacitt/scripts/journey-state.d.mts +3 -0
  41. package/skills/tacitt/scripts/journey-state.mjs +8 -1
  42. package/skills/tacitt/scripts/journey.mjs +31 -1
  43. package/skills/tacitt/scripts/privacy-validator.mjs +160 -40
  44. package/skills/tacitt/scripts/review.d.mts +1 -0
  45. package/skills/tacitt/scripts/review.mjs +296 -35
  46. package/dist/web/assets/index-BgDdPL1c.js +0 -172
@@ -12,6 +12,7 @@ import {
12
12
  interruptedReviews,
13
13
  isIncluded,
14
14
  leftOutField,
15
+ accountFormationField,
15
16
  activeTerms,
16
17
  stageFingerprint,
17
18
  procedureFor,
@@ -87,6 +88,27 @@ async function prepareInitialFreeze(run, journeyId, call, view) {
87
88
  } catch (error) {
88
89
  // A large first freeze can outlast one helper command; captured roots persist for the rerun.
89
90
  if (error?.code === 'CLI_UNAVAILABLE') return { stage: 'freeze', status: 'running', startedAt }
91
+ // Every session is already on the profile or left out (ADR 0276): there is no Send.
92
+ if (error?.code === 'ACCOUNT_NOTHING_TO_SEND') {
93
+ const stopped = await call('journey.get', { journeyId })
94
+ return {
95
+ stage: 'freeze',
96
+ status: 'nothing-to-send',
97
+ startedAt,
98
+ ...accountFormationField(stopped),
99
+ }
100
+ }
101
+ if (['ACCOUNT_WORKS_UNAVAILABLE', 'ACCOUNT_SIGNED_OUT'].includes(error?.code)) {
102
+ const stopped = await call('journey.get', { journeyId })
103
+ if (stopped.accountReadRecovery)
104
+ return {
105
+ stage: 'freeze',
106
+ status: 'blocked',
107
+ startedAt,
108
+ accountReadRecovery: stopped.accountReadRecovery,
109
+ next: 'journey.prepare',
110
+ }
111
+ }
90
112
  throw error
91
113
  }
92
114
  const completed = await call('journey.get', { journeyId })
@@ -194,12 +216,16 @@ async function awaitDecision(run, journeyId, waitMs, call, sleep, ensureHost) {
194
216
  command: 'await',
195
217
  from,
196
218
  phase,
219
+ ...(view.accountReadRecovery
220
+ ? { accountReadRecovery: view.accountReadRecovery, next: 'journey.prepare' }
221
+ : {}),
197
222
  changed,
198
223
  waitedMs: Date.now() - started,
199
224
  revision: view.revision,
200
225
  blockedWorks: blocked,
201
226
  ...(held.length ? { interruptedReviews: held } : {}),
202
227
  ...leftOutField(view),
228
+ ...accountFormationField(view),
203
229
  ...(retried.length ? { retriedWorks: retried, next: 'journey.prepare' } : {}),
204
230
  ...(handoff ? { handoff: { panel: await ensureHost(), next: 'end-turn' } } : {}),
205
231
  }
@@ -212,7 +238,7 @@ async function runPhaseCore(
212
238
  { run, journeyId, command, stage, refresh = false, limit = 3, waitMs = 0, sleep, ensureHost },
213
239
  call,
214
240
  ) {
215
- if (!Number.isSafeInteger(limit) || limit < 1 || limit > 16) throw new Error('INVALID_ARGUMENTS')
241
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 24) throw new Error('INVALID_ARGUMENTS')
216
242
  // Append-batch consolidation uses immutable local drafts. No current journey view is
217
243
  // needed until publication; retain write recovery and live value settlement on that path.
218
244
  if (stage === 'cards' && !refresh && ['consolidate', 'ingest'].includes(command)) {
@@ -264,12 +290,16 @@ async function runPhaseCore(
264
290
  ? { classification: await classificationProgress(manifest) }
265
291
  : {}),
266
292
  phase: await currentPhase(run, view),
293
+ ...(view.accountReadRecovery
294
+ ? { accountReadRecovery: view.accountReadRecovery, next: 'journey.prepare' }
295
+ : {}),
267
296
  revision: view.revision,
268
297
  readingComplete: view.readingComplete,
269
298
  pending: activeTerms(view).filter((term) => term.disposition === 'pending').length,
270
299
  blockedWorks: blockedWorks(view),
271
300
  ...(interruptions.length ? { interruptedReviews: interruptions } : {}),
272
301
  ...leftOutField(view),
302
+ ...accountFormationField(view),
273
303
  answersRevision: view.privacy.answersRevision,
274
304
  ...packagingProgress(view),
275
305
  coldElapsedMs,
@@ -5112,6 +5112,14 @@ function object(shape, params) {
5112
5112
  };
5113
5113
  return new ZodObject(def);
5114
5114
  }
5115
+ function strictObject(shape, params) {
5116
+ return new ZodObject({
5117
+ type: "object",
5118
+ shape,
5119
+ catchall: never(),
5120
+ ...normalizeParams(params)
5121
+ });
5122
+ }
5115
5123
  function looseObject(shape, params) {
5116
5124
  return new ZodObject({
5117
5125
  type: "object",
@@ -5400,7 +5408,7 @@ const range = object({
5400
5408
  start: number().int().nonnegative(),
5401
5409
  end: number().int().nonnegative()
5402
5410
  }).strict();
5403
- const digest$3 = string().regex(/^[a-f0-9]{64}$/u);
5411
+ const digest$4 = string().regex(/^[a-f0-9]{64}$/u);
5404
5412
  /** Original static title mask provenance for keyword-only preview/packaging. */
5405
5413
  const classificationTitleMaskSchema = object({
5406
5414
  inputRange: range,
@@ -5417,14 +5425,66 @@ const classificationTitleMaskSchema = object({
5417
5425
  originalPointer: string().nullable(),
5418
5426
  outputPointer: string().nullable(),
5419
5427
  sourceRange: range.nullable(),
5420
- inputSha256: digest$3,
5421
- outputSha256: digest$3,
5428
+ inputSha256: digest$4,
5429
+ outputSha256: digest$4,
5422
5430
  context: object({
5423
5431
  start: number().int().nonnegative(),
5424
5432
  text: string()
5425
5433
  }).strict()
5426
5434
  }).strict();
5427
5435
  //#endregion
5436
+ //#region packages/application/src/journey/account-works.ts
5437
+ const digest$3 = string().regex(/^[0-9a-f]{64}$/u);
5438
+ const stored = object({
5439
+ /** Wire field from issue #131; a formation reply without it is read as no stored metadata. */
5440
+ metadataSha256: digest$3.nullish().transform((value) => value ?? null),
5441
+ key: digest$3,
5442
+ sessionKey: string().regex(/^sess-pilot\/[^/]+\/[^/]+\/[^/]+$/u).nullable(),
5443
+ sha256: digest$3,
5444
+ byteLength: number().int().positive(),
5445
+ lastAt: datetime()
5446
+ });
5447
+ const access = _enum([
5448
+ "public",
5449
+ "private",
5450
+ "removed"
5451
+ ]);
5452
+ const accountWorksInventorySchema = object({
5453
+ works: array(object({
5454
+ logicalWorkId: uuid(),
5455
+ title: string().min(1).max(300),
5456
+ access,
5457
+ unitAccess: access,
5458
+ sessions: array(stored)
5459
+ })),
5460
+ unassigned: array(stored.extend({ removedFromWork: uuid().nullish().transform((value) => value ?? null) }))
5461
+ });
5462
+ const accountWorksSchema = discriminatedUnion("status", [
5463
+ strictObject({
5464
+ status: literal("read"),
5465
+ subject: string().min(1).max(128),
5466
+ at: datetime(),
5467
+ inventory: accountWorksInventorySchema,
5468
+ /** Families with a key unassigned after its Work's removal: no Work, not sent (D4). */
5469
+ leftOut: array(strictObject({
5470
+ rootSessionId: string().min(1).max(128),
5471
+ removedWorkId: uuid()
5472
+ })).default([]),
5473
+ /** Families with a held key that are not cleanly placed: no Work, not sent (D9). */
5474
+ heldBack: array(string().min(1).max(128)).default([]),
5475
+ /** Every family was left out or held back: no Work and no snapshot (#129 review). */
5476
+ nothingToSend: literal(true).optional()
5477
+ }),
5478
+ strictObject({
5479
+ status: literal("signed-out"),
5480
+ at: datetime()
5481
+ }),
5482
+ strictObject({
5483
+ status: literal("unavailable"),
5484
+ at: datetime()
5485
+ })
5486
+ ]);
5487
+ //#endregion
5428
5488
  //#region packages/application/src/journey/profile.ts
5429
5489
  /**
5430
5490
  * Passport's own rules (talent-platform `profile-settings.ts`): 1 to 39 letters and digits with
@@ -9833,6 +9893,8 @@ const decisionCardSchema = decisionCardInputSchema.extend({ answer: object({
9833
9893
  rule: string().max(4001),
9834
9894
  additionalRule: string().max(2e3),
9835
9895
  remember: boolean(),
9896
+ /** Where the answer came from; only the person's panel Hide is remembered (issue #126). */
9897
+ source: _enum(["conversation", "panel"]).optional(),
9836
9898
  excludedTermKeys: array(string().min(1)).max(2e4).optional()
9837
9899
  }).strict().nullable() }).strict();
9838
9900
  const journeyPrivacySchema = object({
@@ -10165,7 +10227,8 @@ const reviewInterruptionSchema = object({
10165
10227
  }).strict();
10166
10228
  const journeyWorkSchema = object({
10167
10229
  id: journeyIdSchema,
10168
- title: label,
10230
+ /** 240 characters; an `account` title holds the server's 300 (ADR 0276 D2). */
10231
+ title: string().trim().min(1).max(300),
10169
10232
  /** Statically processed repository or folder name; the original stays in the private plan. */
10170
10233
  projectName: label,
10171
10234
  projectType: journeyWorkTypeSchema.nullable(),
@@ -10181,7 +10244,11 @@ const journeyWorkSchema = object({
10181
10244
  description: string(),
10182
10245
  reason: string()
10183
10246
  }).strict().optional(),
10184
- titleOrigin: _enum(["model", "explicit"]),
10247
+ titleOrigin: _enum([
10248
+ "model",
10249
+ "explicit",
10250
+ "account"
10251
+ ]),
10185
10252
  explicitTitleId: journeyIdSchema.nullable(),
10186
10253
  accessOrigin: _enum([
10187
10254
  "default",
@@ -10219,6 +10286,8 @@ const journeyWorkSchema = object({
10219
10286
  inputRevision: count$1.min(1),
10220
10287
  /** Placement created this Work (create-work or a sorting "new" decision): its name is generic. */
10221
10288
  createdByPlacement: literal(true).optional(),
10289
+ /** Rebuilt at formation from the account Work with this id (ADR 0276 D1); never classified. */
10290
+ accountWork: literal(true).optional(),
10222
10291
  /** Host-owned eligibility after removal-only regrouping; never authorizes output by itself. */
10223
10292
  reviewReuseFrom: object({
10224
10293
  inputRevision: count$1.min(1),
@@ -10274,7 +10343,7 @@ const journeyWorkSchema = object({
10274
10343
  sentBundleId: journeyIdSchema.optional(),
10275
10344
  sentAccess: _enum(["public", "private"]).optional()
10276
10345
  }).strict().nullable()
10277
- }).strict();
10346
+ }).strict().refine((work) => work.titleOrigin === "account" || work.title.length <= 240, { path: ["title"] });
10278
10347
  const journeyConsentSchema = object({
10279
10348
  provider: label,
10280
10349
  allowedAt: datetime(),
@@ -10296,7 +10365,7 @@ const journeyExceptionSchema = object({
10296
10365
  value: string().min(1).max(1024)
10297
10366
  }).strict();
10298
10367
  object({
10299
- formatVersion: literal(10),
10368
+ formatVersion: literal([10, 11]),
10300
10369
  outputVersion: number().int().positive().optional(),
10301
10370
  classificationRevision: count$1.nullable(),
10302
10371
  privacy: journeyPrivacySchema,
@@ -10340,6 +10409,11 @@ object({
10340
10409
  /** ADR 0086: per-value Hide or Keep over the term types' answers; absent means none. */
10341
10410
  valueOverrides: array(journeyValueOverrideSchema).max(1e4).optional(),
10342
10411
  /**
10412
+ * Values the person hid in earlier journeys on this machine, frozen at formation (issue #126).
10413
+ * Private: never in a view, reader guidance or anything sent.
10414
+ */
10415
+ rememberedHides: array(string().min(1).max(1024)).max(1e5).optional(),
10416
+ /**
10343
10417
  * ADR 0086: the snapshot is read by dictionary review. A snapshot without it was read by B
10344
10418
  * review, whose evidence this release no longer renders, so it is obsolete.
10345
10419
  */
@@ -10361,6 +10435,8 @@ object({
10361
10435
  * confirmation and Retry set `read`; late classification records only a failure. Absent in a
10362
10436
  * journey of an earlier version, which reads as not `read`.
10363
10437
  */
10438
+ /** Formation read only; never establishes access proof. */
10439
+ accountWorks: accountWorksSchema.optional(),
10364
10440
  accountRead: object({
10365
10441
  status: _enum([
10366
10442
  "read",
@@ -10644,43 +10720,82 @@ object({
10644
10720
  reviewBatchSchema.omit({ digest: true });
10645
10721
  //#endregion
10646
10722
  //#region packages/application/src/journey/terms/locate.ts
10647
- /** NFC core text of each analysed field, contiguous core chunks joined, with its Work. */
10648
- function coreTexts(batches) {
10649
- const texts = [];
10723
+ /**
10724
+ * Issue #140: per batch and field, contiguous core and context chunks in offset order, never
10725
+ * joined across fields, field revisions or gaps. A core boundary maps to the NFC length of the
10726
+ * text before it.
10727
+ */
10728
+ function* termSearchRuns(batches) {
10650
10729
  for (const batch of batches) {
10651
- const fields = Map.groupBy(batch.chunks.filter((chunk) => chunk.core), (chunk) => JSON.stringify([chunk.fieldId, chunk.fieldHash]));
10652
- for (const chunks of fields.values()) {
10653
- let end = -1, text = "";
10654
- for (const chunk of chunks.toSorted((a, b) => a.offset - b.offset)) {
10655
- if (chunk.offset !== end && text) {
10656
- texts.push({
10657
- workId: batch.workId,
10658
- text: text.normalize("NFC")
10659
- });
10660
- text = "";
10730
+ const fields = Map.groupBy(batch.chunks, (chunk) => JSON.stringify([chunk.fieldId, chunk.fieldHash]));
10731
+ for (const group of fields.values()) {
10732
+ let run = [];
10733
+ const runs = [run];
10734
+ for (const chunk of group.toSorted((a, b) => a.offset - b.offset)) {
10735
+ const last = run.at(-1);
10736
+ if (last && last.offset + last.text.length !== chunk.offset) runs.push(run = []);
10737
+ run.push(chunk);
10738
+ }
10739
+ for (const chunks of runs) {
10740
+ if (!chunks.some((chunk) => chunk.core)) continue;
10741
+ const raw = chunks.map((chunk) => chunk.text).join("");
10742
+ const text = raw.normalize("NFC");
10743
+ const at = (end) => Math.min(text.length, raw.slice(0, end).normalize("NFC").length);
10744
+ const cores = [];
10745
+ let offset = 0;
10746
+ for (const chunk of chunks) {
10747
+ const start = offset;
10748
+ offset += chunk.text.length;
10749
+ if (!chunk.core) continue;
10750
+ const last = cores.at(-1);
10751
+ if (last && last[1] === start) last[1] = offset;
10752
+ else cores.push([start, offset]);
10661
10753
  }
10662
- text += chunk.text;
10663
- end = chunk.offset + chunk.text.length;
10754
+ yield {
10755
+ batch,
10756
+ chunks,
10757
+ text,
10758
+ cores: cores.map(([start, end]) => [at(start), at(end)])
10759
+ };
10664
10760
  }
10665
- if (text) texts.push({
10666
- workId: batch.workId,
10667
- text: text.normalize("NFC")
10668
- });
10669
10761
  }
10670
10762
  }
10671
- return texts;
10672
10763
  }
10673
- function occurrences(text, term) {
10674
- let count = 0;
10675
- for (let at = text.indexOf(term); at >= 0; at = text.indexOf(term, at + term.length)) count++;
10676
- return count;
10764
+ /**
10765
+ * Occurrences of `value` in a run: `owned` counts non-overlapping occurrences that start in a
10766
+ * core range; `first` is the first occurrence overlapping a core range, or -1. An occurrence that
10767
+ * starts outside the core is skipped by one position only, so it never hides an owned one.
10768
+ */
10769
+ function runOccurrences(run, value) {
10770
+ let owned = 0, first = -1;
10771
+ if (!value) return {
10772
+ owned,
10773
+ first
10774
+ };
10775
+ for (let at = run.text.indexOf(value); at >= 0;) {
10776
+ const end = at + value.length;
10777
+ if (run.cores.some(([start, stop]) => at >= start && at < stop)) {
10778
+ owned++;
10779
+ if (first < 0) first = at;
10780
+ at = run.text.indexOf(value, end);
10781
+ continue;
10782
+ }
10783
+ if (first < 0 && run.cores.some(([start, stop]) => at < stop && end > start)) first = at;
10784
+ at = run.text.indexOf(value, at + 1);
10785
+ }
10786
+ return {
10787
+ owned,
10788
+ first
10789
+ };
10677
10790
  }
10678
10791
  /**
10679
- * ADR 0086: a value is kept only if it occurs verbatim (NFC, case-sensitive) in a core chunk of
10680
- * the pack's units; its complete admitted NFC value becomes one dictionary entry (ADR 0088).
10792
+ * ADR 0086, refined by issue #140: a value is kept only if it occurs verbatim (NFC,
10793
+ * case-sensitive) in a pack field's contiguous core and context text, overlapping a core chunk;
10794
+ * only occurrences that start in a core chunk are counted. Its complete admitted NFC value
10795
+ * becomes one dictionary entry (ADR 0088).
10681
10796
  */
10682
10797
  function locateTerms(batches, terms) {
10683
- const texts = coreTexts(batches);
10798
+ const runs = [...termSearchRuns(batches)];
10684
10799
  const located = /* @__PURE__ */ new Map();
10685
10800
  const unlocatedIndices = [];
10686
10801
  let kept = 0, unlocated = 0;
@@ -10693,20 +10808,25 @@ function locateTerms(batches, terms) {
10693
10808
  }];
10694
10809
  }));
10695
10810
  for (const { type, value, index } of unique.values()) {
10696
- if (value.includes("█") || admitCustomTerm(value)?.value !== value || !texts.some((item) => item.text.includes(value))) {
10811
+ let count = 0;
10812
+ let workId;
10813
+ if (!value.includes("█") && admitCustomTerm(value)?.value === value) for (const run of runs) {
10814
+ const { owned, first } = runOccurrences(run, value);
10815
+ if (first < 0) continue;
10816
+ count += owned;
10817
+ workId ??= run.batch.workId;
10818
+ }
10819
+ if (workId === void 0) {
10697
10820
  unlocated++;
10698
10821
  unlocatedIndices.push(index);
10699
10822
  continue;
10700
10823
  }
10701
10824
  kept++;
10702
- const holders = texts.filter((item) => item.text.includes(value));
10703
- const first = holders[0];
10704
- if (!first) continue;
10705
10825
  located.set(JSON.stringify([type, value]), {
10706
10826
  type,
10707
10827
  value,
10708
- count: holders.reduce((sum, item) => sum + occurrences(item.text, value), 0),
10709
- workId: first.workId
10828
+ count,
10829
+ workId
10710
10830
  });
10711
10831
  }
10712
10832
  return {
@@ -85,4 +85,5 @@ export function writePackTask(
85
85
  }[]
86
86
  }[],
87
87
  attemptId?: string,
88
+ reader?: { readerId?: string; readerTurn?: string; repair?: boolean },
88
89
  ): Promise<string>