@indigoai-us/hq-cli 5.124.0 → 5.126.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.
Files changed (80) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/assets/bot-workers/setup/skills/joining-a-team.md +125 -0
  3. package/assets/bot-workers/setup/skills/your-business.md +270 -0
  4. package/assets/bot-workers/setup/worker.yaml +189 -58
  5. package/dist/command-catalog.generated.d.ts +414 -5
  6. package/dist/command-catalog.generated.js +531 -5
  7. package/dist/command-registration-plan.d.ts +6 -0
  8. package/dist/command-registration-plan.js +1 -0
  9. package/dist/commands/bot.d.ts +10 -0
  10. package/dist/commands/bot.js +6 -0
  11. package/dist/commands/lanes.d.ts +75 -0
  12. package/dist/commands/lanes.js +2032 -0
  13. package/dist/commands/login.js +49 -6
  14. package/dist/commands/mesh.js +133 -28
  15. package/dist/commands/onboard-create-identity.d.ts +26 -0
  16. package/dist/commands/onboard-create-identity.js +23 -0
  17. package/dist/commands/onboard.js +13 -4
  18. package/dist/lib/bot/progress.d.ts +6 -0
  19. package/dist/lib/bot/progress.js +16 -0
  20. package/dist/lib/bot/runtime/grok.d.ts +30 -7
  21. package/dist/lib/bot/runtime/grok.js +86 -8
  22. package/dist/lib/bot/runtime/index.js +15 -8
  23. package/dist/lib/bot/runtime/messages-stream.d.ts +12 -3
  24. package/dist/lib/bot/runtime/messages-stream.js +49 -3
  25. package/dist/lib/doctor/checks/work-context.js +4 -2
  26. package/dist/lib/lanes/accounts.d.ts +43 -0
  27. package/dist/lib/lanes/accounts.js +128 -0
  28. package/dist/lib/lanes/config.d.ts +56 -0
  29. package/dist/lib/lanes/config.js +351 -0
  30. package/dist/lib/lanes/errors.d.ts +39 -0
  31. package/dist/lib/lanes/errors.js +72 -0
  32. package/dist/lib/lanes/index.d.ts +19 -0
  33. package/dist/lib/lanes/index.js +12 -0
  34. package/dist/lib/lanes/liveness.d.ts +89 -0
  35. package/dist/lib/lanes/liveness.js +273 -0
  36. package/dist/lib/lanes/registry.d.ts +103 -0
  37. package/dist/lib/lanes/registry.js +247 -0
  38. package/dist/lib/lanes/store.d.ts +64 -0
  39. package/dist/lib/lanes/store.js +627 -0
  40. package/dist/lib/lanes/throttle.d.ts +89 -0
  41. package/dist/lib/lanes/throttle.js +297 -0
  42. package/dist/lib/lanes/types.d.ts +193 -0
  43. package/dist/lib/lanes/types.js +217 -0
  44. package/dist/lib/mesh/live/backfill-held.d.ts +2 -0
  45. package/dist/lib/mesh/live/backfill-held.js +8 -4
  46. package/dist/lib/mesh/live/daemon/board-refresh.d.ts +3 -0
  47. package/dist/lib/mesh/live/daemon/board-refresh.js +6 -0
  48. package/dist/lib/mesh/live/daemon/config.d.ts +27 -0
  49. package/dist/lib/mesh/live/daemon/config.js +75 -0
  50. package/dist/lib/mesh/live/daemon/doctor.d.ts +3 -0
  51. package/dist/lib/mesh/live/daemon/doctor.js +21 -1
  52. package/dist/lib/mesh/live/daemon/index.d.ts +4 -3
  53. package/dist/lib/mesh/live/daemon/index.js +3 -3
  54. package/dist/lib/mesh/live/daemon/install.d.ts +143 -1
  55. package/dist/lib/mesh/live/daemon/install.js +444 -2
  56. package/dist/lib/mesh/live/daemon/paths.d.ts +2 -0
  57. package/dist/lib/mesh/live/daemon/paths.js +2 -0
  58. package/dist/lib/mesh/live/daemon/run.d.ts +11 -0
  59. package/dist/lib/mesh/live/daemon/run.js +129 -10
  60. package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
  61. package/dist/lib/mesh/live/flush.d.ts +3 -0
  62. package/dist/lib/mesh/live/flush.js +11 -0
  63. package/dist/lib/scan-packages/index.js +17 -12
  64. package/dist/lib/work-context/company.d.ts +8 -0
  65. package/dist/lib/work-context/company.js +62 -18
  66. package/dist/lib/work-context/config.d.ts +55 -0
  67. package/dist/lib/work-context/config.js +333 -67
  68. package/dist/lib/work-context/contract.d.ts +2 -0
  69. package/dist/lib/work-context/default-company.d.ts +43 -0
  70. package/dist/lib/work-context/default-company.js +83 -0
  71. package/dist/lib/work-context/outbox.d.ts +5 -1
  72. package/dist/lib/work-context/outbox.js +5 -0
  73. package/dist/lib/work-context/reconcile.d.ts +4 -0
  74. package/dist/lib/work-context/reconcile.js +86 -9
  75. package/dist/lib/work-context/state.d.ts +2 -0
  76. package/dist/lib/work-context/state.js +14 -0
  77. package/dist/lib/work-context/windows-acl.d.ts +123 -0
  78. package/dist/lib/work-context/windows-acl.js +425 -0
  79. package/dist/utils/pack-contributions.js +5 -4
  80. package/package.json +1 -1
@@ -21,6 +21,7 @@ export const OUTBOX_ALLOWLIST = [
21
21
  "companySlug",
22
22
  "projectId",
23
23
  "taskId",
24
+ "companyConfidence",
24
25
  "kind",
25
26
  "digest",
26
27
  "delivery",
@@ -96,6 +97,8 @@ function projectOutbox(op) {
96
97
  out.projectId = op.projectId;
97
98
  if (op.taskId)
98
99
  out.taskId = op.taskId;
100
+ if (op.companyConfidence)
101
+ out.companyConfidence = op.companyConfidence;
99
102
  if (op.lastErrorCode)
100
103
  out.lastErrorCode = op.lastErrorCode;
101
104
  if (op.receiptId)
@@ -143,6 +146,7 @@ export function enqueueOutbox(input, root) {
143
146
  companySlug: input.companySlug,
144
147
  projectId: input.projectId,
145
148
  taskId: input.taskId,
149
+ companyConfidence: input.companyConfidence,
146
150
  kind,
147
151
  destinationCompanyUid: input.destinationCompanyUid,
148
152
  destinationCompanySlug: input.destinationCompanySlug,
@@ -172,6 +176,7 @@ export function enqueueOutbox(input, root) {
172
176
  companySlug: input.companySlug,
173
177
  projectId: input.projectId,
174
178
  taskId: input.taskId,
179
+ companyConfidence: input.companyConfidence,
175
180
  kind,
176
181
  digest,
177
182
  delivery: "queued",
@@ -52,6 +52,10 @@ export interface ReconcileDeps {
52
52
  root: string;
53
53
  env?: NodeJS.ProcessEnv;
54
54
  now?: () => Date;
55
+ /** Used only by explicit historical repair; ordinary reconciliation keeps defaults enabled. */
56
+ allowDeviceDefault?: boolean;
57
+ /** Mark explicit historical default repair as lower-confidence local evidence. */
58
+ deviceDefaultConfidence?: boolean;
55
59
  /** Network deliverer; only called after outbox write. */
56
60
  deliver?: DeliverFn;
57
61
  /** Skip network even when company is resolved (local unit tests). */
@@ -5,6 +5,7 @@
5
5
  import * as fs from "node:fs";
6
6
  import { InvalidSessionIdentityError, normalizeSessionIdentity, } from "../mesh/live/session-identity.js";
7
7
  import { companyCorrectionPath, companySlugFromCwd, resolveCompany, } from "./company.js";
8
+ import { invalidateDefaultCompanyMemberships, } from "./config.js";
8
9
  import { WORK_CONTEXT_CONTRACT_VERSION, normalizeTaskId, } from "./contract.js";
9
10
  import { EXIT_INVALID_IDENTITY, EXIT_NOT_TRACKING, EXIT_OK, InvalidDecisionOriginError, NotTrackingError, } from "./errors.js";
10
11
  import { decisionFromCandidates, } from "./organize.js";
@@ -24,6 +25,8 @@ function resultOf(parts) {
24
25
  };
25
26
  if (parts.companyUid)
26
27
  out.companyUid = parts.companyUid;
28
+ if (parts.companySlug)
29
+ out.companySlug = parts.companySlug;
27
30
  if (parts.projectId)
28
31
  out.projectId = parts.projectId;
29
32
  if (parts.taskId)
@@ -222,6 +225,7 @@ export async function reconcileObservation(obs, deps) {
222
225
  hqRoot,
223
226
  remoteOwnerSlug,
224
227
  repoIdentityKey,
228
+ allowDeviceDefault: deps.allowDeviceDefault,
225
229
  existingState: prior,
226
230
  });
227
231
  const nowIso = nowFn().toISOString();
@@ -341,8 +345,26 @@ export async function reconcileObservation(obs, deps) {
341
345
  };
342
346
  }
343
347
  // Membership-validated slug→uid when local resolution only yielded a slug.
348
+ // A device default is weak, cached evidence: validate it authoritatively on
349
+ // every tenant-I/O path so a membership revocation takes effect immediately.
344
350
  let resolvedCompany = company.company;
345
- if (!resolvedCompany.uid && resolvedCompany.slug && deps.validateMembership) {
351
+ const deviceDefaultRequiresValidation = resolvedCompany.source === "device_default";
352
+ if (deviceDefaultRequiresValidation && !deps.validateMembership) {
353
+ return {
354
+ result: resultOf({
355
+ kind: "needs_company",
356
+ classification: "needs_company",
357
+ delivery: "clean",
358
+ sessionId,
359
+ clientOperationId: obs.clientOperationId,
360
+ }),
361
+ exitCode: EXIT_OK,
362
+ diagnostics: ["needs_company: device-default membership validator unavailable"],
363
+ };
364
+ }
365
+ if (resolvedCompany.slug &&
366
+ deps.validateMembership &&
367
+ (!resolvedCompany.uid || deviceDefaultRequiresValidation)) {
346
368
  try {
347
369
  const membership = await deps.validateMembership(resolvedCompany.slug);
348
370
  if (membership && typeof membership === "object" && membership.uid) {
@@ -353,6 +375,9 @@ export async function reconcileObservation(obs, deps) {
353
375
  diagnostics.push(`membership resolved companyUid for slug=${resolvedCompany.slug}`);
354
376
  }
355
377
  else if (membership === false) {
378
+ if (deviceDefaultRequiresValidation) {
379
+ invalidateDefaultCompanyMemberships({ root: deps.root, now: nowFn });
380
+ }
356
381
  diagnostics.push(`membership denied for slug=${resolvedCompany.slug}; treating as needs_company`);
357
382
  const statePath = writeSessionState(mergeLocalSessionFields({
358
383
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
@@ -384,6 +409,19 @@ export async function reconcileObservation(obs, deps) {
384
409
  }
385
410
  catch (err) {
386
411
  diagnostics.push(`membership lookup failed: ${err instanceof Error ? err.message : String(err)}`);
412
+ if (deviceDefaultRequiresValidation) {
413
+ return {
414
+ result: resultOf({
415
+ kind: "needs_company",
416
+ classification: "needs_company",
417
+ delivery: "clean",
418
+ sessionId,
419
+ clientOperationId: obs.clientOperationId,
420
+ }),
421
+ exitCode: EXIT_OK,
422
+ diagnostics: [...diagnostics, "needs_company: device-default membership refresh unavailable"],
423
+ };
424
+ }
387
425
  }
388
426
  }
389
427
  // Company resolved — resolve project/task (US-007B order).
@@ -480,6 +518,11 @@ export async function reconcileObservation(obs, deps) {
480
518
  registerClassification === "unresolved") {
481
519
  registerClassification = "needs_project";
482
520
  }
521
+ // Offline preflight has authoritative membership but deliberately does not
522
+ // invent a project. Expose that usable, validated state to hook consumers.
523
+ if (resolvedCompany.source === "device_default" && deps.offline && registerClassification === "unresolved") {
524
+ registerClassification = "needs_project";
525
+ }
483
526
  // Do not let a weak later observation erase a server-acknowledged company
484
527
  // binding. Keep the result unresolved so the normal candidate threshold can
485
528
  // still advance the session, while preserving the durable emitter projection.
@@ -544,6 +587,7 @@ export async function reconcileObservation(obs, deps) {
544
587
  sessionId,
545
588
  clientOperationId: obs.clientOperationId,
546
589
  companyUid: resolvedCompany.uid,
590
+ companySlug: resolvedCompany.slug,
547
591
  projectId,
548
592
  taskId,
549
593
  bindingEpisodeId: acknowledgedPrior.bindingEpisodeId,
@@ -553,6 +597,9 @@ export async function reconcileObservation(obs, deps) {
553
597
  };
554
598
  }
555
599
  let outboxOp;
600
+ const companyConfidence = company.status === "resolved" && company.company.source === "device_default"
601
+ ? "device_default"
602
+ : undefined;
556
603
  try {
557
604
  outboxOp = enqueueOutbox({
558
605
  clientOperationId: obs.clientOperationId,
@@ -562,6 +609,7 @@ export async function reconcileObservation(obs, deps) {
562
609
  companySlug: resolvedCompany.slug,
563
610
  projectId,
564
611
  taskId,
612
+ companyConfidence,
565
613
  kind: "register",
566
614
  now: nowFn,
567
615
  }, deps.root);
@@ -588,8 +636,9 @@ export async function reconcileObservation(obs, deps) {
588
636
  diagnostics.push(`project source=${projectResolved.source} projectId=${projectId ?? "-"} taskId=${taskId ?? "-"}`);
589
637
  }
590
638
  let statePath;
639
+ let stateWrite;
591
640
  try {
592
- const stateWrite = mergeLocalSessionFields({
641
+ stateWrite = mergeLocalSessionFields({
593
642
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
594
643
  sessionId,
595
644
  companyUid: resolvedCompany.uid,
@@ -602,6 +651,7 @@ export async function reconcileObservation(obs, deps) {
602
651
  ...(typeof toolWrites === "number" ? { toolWrites } : {}),
603
652
  ...(hasTurn ? { hasTurn: true } : {}),
604
653
  ...(decision ? { decision } : {}),
654
+ ...(companyConfidence ? { companyConfidence } : {}),
605
655
  ...transcriptFields,
606
656
  }, prior);
607
657
  statePath = writeSessionState(stateWrite, deps.root);
@@ -616,6 +666,7 @@ export async function reconcileObservation(obs, deps) {
616
666
  sessionId,
617
667
  clientOperationId: obs.clientOperationId,
618
668
  companyUid: resolvedCompany.uid,
669
+ companySlug: resolvedCompany.slug,
619
670
  projectId,
620
671
  taskId,
621
672
  errorCode: "NOT_TRACKING",
@@ -636,6 +687,7 @@ export async function reconcileObservation(obs, deps) {
636
687
  sessionId,
637
688
  clientOperationId: obs.clientOperationId,
638
689
  companyUid: resolvedCompany.uid,
690
+ companySlug: resolvedCompany.slug,
639
691
  projectId,
640
692
  taskId,
641
693
  }),
@@ -649,23 +701,47 @@ export async function reconcileObservation(obs, deps) {
649
701
  if (delivered.ok) {
650
702
  markOutboxAcked(outboxOp.operationId, deps.root, delivered.receiptId, nowFn);
651
703
  writeAckedRegisterState(outboxOp, deps.root, delivered.receiptId, nowFn);
652
- // Persist binding episode on success for bound registrations.
653
- if (registerClassification === "bound") {
704
+ // Company attribution write for EVERY acked register that resolved a
705
+ // company — not only bound sessions. A company-only ack (needs_project /
706
+ // needs_task / unresolved) must still project companyUid/companySlug and
707
+ // the true computed classification into durable session state; otherwise
708
+ // the emitter and mesh presence lose the tenant the server already
709
+ // acknowledged. projectId / taskId are written only when genuinely
710
+ // resolved, and a fresh bindingEpisodeId only when the session is bound —
711
+ // never fabricated on a partial path. (writeAckedRegisterState above may
712
+ // have already recorded a receipt-derived episode for a resolved partial
713
+ // binding; the default merge preserves that when the binding still matches.)
714
+ if (resolvedCompany.uid) {
715
+ const bound = registerClassification === "bound";
716
+ // A server ack of the company makes it authoritative: the durable status
717
+ // is at least needs_project (company known, no project), needs_task
718
+ // (project, no task), or bound (project + task) — mirroring
719
+ // acknowledgedStatus() in state.ts. Never downgrade an acked company to a
720
+ // pre-threshold "unresolved" projection; that is what dropped the tenant.
721
+ const ackedClassification = projectId && taskId
722
+ ? "bound"
723
+ : projectId
724
+ ? "needs_task"
725
+ : "needs_project";
654
726
  writeSessionState(mergeLocalSessionFields({
655
727
  contractVersion: WORK_CONTEXT_CONTRACT_VERSION,
656
728
  sessionId,
657
729
  companyUid: resolvedCompany.uid,
658
730
  companySlug: resolvedCompany.slug,
659
- contextStatus: "bound",
660
- projectId,
661
- taskId,
662
- bindingEpisodeId: delivered.receiptId,
731
+ contextStatus: bound ? "bound" : ackedClassification,
732
+ ...(projectId ? { projectId } : {}),
733
+ ...(taskId ? { taskId } : {}),
734
+ ...(bound ? { bindingEpisodeId: delivered.receiptId } : {}),
663
735
  updatedAt: nowFn().toISOString(),
664
736
  startedAt,
665
737
  ...(typeof toolWrites === "number" ? { toolWrites } : {}),
666
738
  ...(hasTurn ? { hasTurn: true } : {}),
667
739
  ...transcriptFields,
668
- }, prior), deps.root);
740
+ },
741
+ // Merge against the just-written projection rather than the stale
742
+ // pre-reconcile snapshot, so device confidence and any receipt
743
+ // attribution survive.
744
+ readSessionState(sessionId, deps.root) ?? stateWrite), deps.root);
669
745
  }
670
746
  const kind = registerClassification === "bound"
671
747
  ? "bound"
@@ -721,6 +797,7 @@ export async function reconcileObservation(obs, deps) {
721
797
  sessionId,
722
798
  clientOperationId: obs.clientOperationId,
723
799
  companyUid: resolvedCompany.uid,
800
+ companySlug: resolvedCompany.slug,
724
801
  projectId,
725
802
  taskId,
726
803
  errorCode: delivered.code,
@@ -89,6 +89,8 @@ export interface SessionStateFile {
89
89
  * before SESSION_STATE sync (kept after AMEND-001).
90
90
  */
91
91
  transcriptMarker?: "from transcript";
92
+ /** Historical repair under device-default evidence is intentionally weak. */
93
+ companyConfidence?: "device_default";
92
94
  updatedAt: string;
93
95
  }
94
96
  /**
@@ -27,6 +27,7 @@ export const SESSION_STATE_ALLOWLIST = new Set([
27
27
  "source",
28
28
  "localHarness",
29
29
  "transcriptMarker",
30
+ "companyConfidence",
30
31
  "updatedAt",
31
32
  ]);
32
33
  export function readSessionState(sessionId, root) {
@@ -100,6 +101,8 @@ export function projectSessionState(state) {
100
101
  out.localHarness = state.localHarness;
101
102
  if (state.transcriptMarker)
102
103
  out.transcriptMarker = state.transcriptMarker;
104
+ if (state.companyConfidence)
105
+ out.companyConfidence = state.companyConfidence;
103
106
  return out;
104
107
  }
105
108
  /**
@@ -148,6 +151,13 @@ export function mergeLocalSessionFields(next, prior, options = {}) {
148
151
  if (!out.transcriptMarker && prior.transcriptMarker) {
149
152
  out.transcriptMarker = prior.transcriptMarker;
150
153
  }
154
+ const sameCompanyBinding = Boolean(prior.companyUid && out.companyUid && prior.companyUid === out.companyUid) ||
155
+ Boolean(prior.companySlug &&
156
+ out.companySlug &&
157
+ prior.companySlug.toLowerCase() === out.companySlug.toLowerCase());
158
+ if (!out.companyConfidence && prior.companyConfidence && sameCompanyBinding) {
159
+ out.companyConfidence = prior.companyConfidence;
160
+ }
151
161
  const sameBinding = prior.companyUid === out.companyUid &&
152
162
  prior.companySlug === out.companySlug &&
153
163
  prior.projectId === out.projectId &&
@@ -237,6 +247,10 @@ export function isHookWrittenSessionState(state) {
237
247
  export function authoritativeCompanyFromState(state) {
238
248
  if (!state)
239
249
  return null;
250
+ // A binding created only from the device default is deliberately weak. A
251
+ // later /startwork meta designation or remembered repo choice may replace it.
252
+ if (state.companyConfidence === "device_default")
253
+ return null;
240
254
  const writable = new Set([
241
255
  "unresolved",
242
256
  "needs_project",
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Windows DACL check for the work-context config (US-018).
3
+ *
4
+ * NTFS has no POSIX mode bits: Node reports `mode` 0666 for every regular
5
+ * file on win32, so the POSIX `mode & 0o022` test in config.ts can never pass
6
+ * there. Instead of skipping the check on Windows, this module reads the real
7
+ * DACL and refuses the config when any principal other than the current user,
8
+ * SYSTEM, Administrators, or the owner is granted write/modify/full access.
9
+ *
10
+ * How the DACL is read (no native dependency):
11
+ * `icacls <path> /save <tmpfile>` writes the DACL as an SDDL string
12
+ * (`D:AI(A;ID;FA;;;SY)(A;ID;FA;;;BA)(A;ID;FA;;;S-1-5-21-...)`). SDDL carries
13
+ * SIDs or well-known SID abbreviations, never display names, so the check is
14
+ * independent of the OS language (VORDEFINIERT\Administratoren is still BA /
15
+ * S-1-5-32-544). The current user's SID comes from `whoami /user`. Display
16
+ * names are only resolved (via PowerShell) for the error message.
17
+ *
18
+ * Trust is decided by SID: S-1-5-18 (SYSTEM), S-1-5-32-544 (Administrators),
19
+ * S-1-3-0 (CREATOR OWNER), S-1-3-4 (OWNER RIGHTS), the current user's SID and
20
+ * the owner's SID when known.
21
+ *
22
+ * Reduced-guarantee path (explicit, documented): the check does NOT throw and
23
+ * instead emits one stderr diagnostic that the config is unprotected on this
24
+ * platform when, and only when, the DACL cannot be evaluated:
25
+ * - icacls is missing, fails, or its output has no SDDL line (unreadable);
26
+ * - an ACE grants write to a principal whose SID we cannot resolve (an SDDL
27
+ * abbreviation this module does not know), or carries a rights token we
28
+ * cannot decode, or the current user's SID is unknown and the grant is to
29
+ * a non-well-known SID (unresolvable).
30
+ * A DACL that parses and grants write to a resolved SID outside the trusted
31
+ * set always throws UnsafeConfigPathError. Nothing else falls back.
32
+ */
33
+ export interface SddlAce {
34
+ /** ACE type token, e.g. `A` (allow) or `D` (deny). */
35
+ type: string;
36
+ /** ACE flag string, e.g. `OICIID`. */
37
+ flags: string;
38
+ /** Rights field as written, e.g. `FA`, `0x1200a9`, `CCDCLCSWRPWPRC`. */
39
+ rights: string;
40
+ /** Account field as written: full SID or two-letter abbreviation. */
41
+ account: string;
42
+ }
43
+ export type AclReadResult = {
44
+ readable: true;
45
+ sddl: string;
46
+ } | {
47
+ readable: false;
48
+ reason: string;
49
+ };
50
+ export interface WindowsAclCheckOptions {
51
+ /** Current user SID (defaults to `whoami /user`). `null` = unknown. */
52
+ currentUserSid?: string | null;
53
+ /** Owner SID when known (icacls /save does not include the owner). */
54
+ ownerSid?: string | null;
55
+ /** DACL reader; injectable for tests. Defaults to `icacls <path> /save`. */
56
+ readAcl?: (targetPath: string) => AclReadResult;
57
+ /** SID → display name for error messages; injectable for tests. */
58
+ resolveSidName?: (sid: string) => string | null;
59
+ /** Diagnostic sink for the reduced-guarantee path (defaults to stderr). */
60
+ diagnostic?: (line: string) => void;
61
+ }
62
+ export type AclVerdict = {
63
+ kind: "safe";
64
+ } | {
65
+ kind: "unsafe";
66
+ sid: string;
67
+ rights: string;
68
+ } | {
69
+ kind: "unresolvable";
70
+ reason: string;
71
+ };
72
+ export declare const SID_SYSTEM = "S-1-5-18";
73
+ export declare const SID_ADMINISTRATORS = "S-1-5-32-544";
74
+ export declare const SID_CREATOR_OWNER = "S-1-3-0";
75
+ export declare const SID_OWNER_RIGHTS = "S-1-3-4";
76
+ /** Resolve an SDDL account field to a SID, or null when unknown. */
77
+ export declare function sddlAccountToSid(account: string): string | null;
78
+ /** Any of these bits lets the holder change the file or replace it. */
79
+ export declare const WRITE_MASK: number;
80
+ /** Decode an SDDL rights field to an access mask, or null when undecodable. */
81
+ export declare function sddlRightsToMask(rights: string): number | null;
82
+ /**
83
+ * Parse the DACL portion of an SDDL string into ACEs. Accepts a full security
84
+ * descriptor string (`O:...G:...D:...S:...`) or just the `D:` section.
85
+ * Returns null when no DACL section is present or an ACE is malformed.
86
+ */
87
+ export declare function parseSddlDacl(sddl: string): SddlAce[] | null;
88
+ /**
89
+ * Parse the file `icacls <path> /save <file>` writes: UTF-16LE (optional BOM),
90
+ * CRLF, alternating `<name>` / `<sddl>` lines. Returns the SDDL of the first
91
+ * entry (the target itself), or an unreadable result.
92
+ */
93
+ export declare function parseIcaclsSaveOutput(raw: string | Buffer): AclReadResult;
94
+ /**
95
+ * Absolute path of a System32 tool. PATH lookup is not safe here: Git Bash and
96
+ * MSYS environments put a coreutils `whoami` ahead of Windows' `whoami.exe`,
97
+ * which does not understand `/user` and would make the current user SID look
98
+ * unknown on every run.
99
+ */
100
+ export declare function system32Tool(exe: string): string;
101
+ export declare function readAclWithIcaclsSave(targetPath: string): AclReadResult;
102
+ /** Current user's SID via `whoami /user /fo csv /nh`; null when unavailable. */
103
+ export declare function readCurrentUserSid(): string | null;
104
+ /** Best-effort SID → account name (only used to build error messages). */
105
+ export declare function resolveSidNameWithPowerShell(sid: string): string | null;
106
+ /**
107
+ * Evaluate parsed ACEs against the trusted SID set.
108
+ * - `unsafe`: an allow ACE grants a write bit to a resolved, untrusted SID.
109
+ * - `unresolvable`: a write-granting ACE whose SID or rights cannot be decoded,
110
+ * or whose SID is a per-machine/domain SID while the current user is unknown.
111
+ * - `safe`: everything else (deny ACEs and read-only grants are ignored).
112
+ */
113
+ export declare function evaluateDacl(aces: SddlAce[], opts: Pick<WindowsAclCheckOptions, "currentUserSid" | "ownerSid">): AclVerdict;
114
+ /**
115
+ * Assert the Windows DACL of `targetPath` grants write only to trusted
116
+ * principals. Throws UnsafeConfigPathError naming the offending principal.
117
+ * Unreadable or unresolvable DACL → reduced-guarantee diagnostic (once per
118
+ * path), no throw. See the module header for the exact conditions.
119
+ */
120
+ export declare function assertWindowsOwnerOnlyAcl(targetPath: string, kind: "file" | "directory", opts?: WindowsAclCheckOptions): void;
121
+ /** Test hook: forget which paths already emitted the reduced-guarantee line. */
122
+ export declare function resetWindowsAclDiagnostics(): void;
123
+ //# sourceMappingURL=windows-acl.d.ts.map