chati-dev 4.5.28 → 4.5.29

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 (172) hide show
  1. package/bin/chati.js +151 -123
  2. package/framework/agents/build/dev.md +4 -4
  3. package/framework/agents/deploy/devops.md +3 -3
  4. package/framework/agents/discover/brief.md +18 -6
  5. package/framework/agents/discover/brownfield-wu.md +3 -3
  6. package/framework/agents/discover/greenfield-wu.md +4 -4
  7. package/framework/agents/plan/architect.md +2 -2
  8. package/framework/agents/plan/detail.md +4 -4
  9. package/framework/agents/plan/phases.md +3 -3
  10. package/framework/agents/plan/tasks.md +47 -19
  11. package/framework/agents/plan/ux-brand-architect.md +1 -1
  12. package/framework/agents/plan/ux.md +26 -2
  13. package/framework/agents/quality/qa-implementation.md +8 -8
  14. package/framework/agents/quality/qa-planning.md +58 -4
  15. package/framework/agents/quality/qa-visual.md +4 -4
  16. package/framework/config.yaml +2 -2
  17. package/framework/constitution.md +12 -1
  18. package/framework/context/governance.md +2 -1
  19. package/framework/context/root.md +3 -1
  20. package/framework/domains/constitution.yaml +1 -1
  21. package/framework/domains/global.yaml +1 -1
  22. package/framework/domains/workflows/standard-flow.yaml +7 -8
  23. package/framework/hooks/advance-trigger.js +3 -2
  24. package/framework/hooks/brief-validator.js +1 -1
  25. package/framework/hooks/post-dev.js +1 -1
  26. package/framework/hooks/team-quality-gate.js +9 -2
  27. package/framework/i18n/en.yaml +1 -0
  28. package/framework/i18n/es.yaml +1 -0
  29. package/framework/i18n/fr.yaml +1 -0
  30. package/framework/i18n/pt.yaml +1 -0
  31. package/framework/manifest.json +83 -83
  32. package/framework/manifest.sig +1 -1
  33. package/framework/orchestrator/chati-router.js +14 -6
  34. package/framework/orchestrator/chati.md +61 -23
  35. package/framework/rules/human-writing-style.md +7 -0
  36. package/framework/schemas/session.schema.json +25 -2
  37. package/framework/schemas/task.schema.json +26 -0
  38. package/framework/tasks/orchestrator-handoff.md +10 -10
  39. package/framework/tasks/orchestrator-resume.md +3 -3
  40. package/framework/workflows/brownfield-fullstack.yaml +6 -3
  41. package/framework/workflows/brownfield-service.yaml +5 -3
  42. package/framework/workflows/brownfield-ui.yaml +5 -3
  43. package/framework/workflows/greenfield-fullstack.yaml +6 -3
  44. package/framework/workflows/standard-flow.yaml +8 -3
  45. package/node_modules/@chati/core/native/browser-proof-wire.h +29 -0
  46. package/node_modules/@chati/core/native/darwin-browser-listener.c +36 -0
  47. package/node_modules/@chati/core/native/darwin-browser-listener.h +32 -0
  48. package/node_modules/@chati/core/native/darwin-browser-proof.h +86 -0
  49. package/node_modules/@chati/core/native/darwin-code-identity.h +35 -0
  50. package/node_modules/@chati/core/native/darwin-launch-messages.h +37 -0
  51. package/node_modules/@chati/core/native/darwin-launcher.c +312 -0
  52. package/node_modules/@chati/core/native/descriptor-fs.c +407 -0
  53. package/node_modules/@chati/core/native/execution-admission.h +59 -0
  54. package/node_modules/@chati/core/native/execution-gate.c +41 -0
  55. package/node_modules/@chati/core/native/linux-process.c +41 -0
  56. package/node_modules/@chati/core/native/prebuilds/darwin-arm64/darwin-launcher +0 -0
  57. package/node_modules/@chati/core/native/prebuilds/darwin-arm64/descriptor-fs.node +0 -0
  58. package/node_modules/@chati/core/native/prebuilds/darwin-arm64/execution-gate +0 -0
  59. package/node_modules/@chati/core/native/prebuilds/linux-x64/descriptor-fs.node +0 -0
  60. package/node_modules/@chati/core/native/prebuilds/linux-x64/execution-gate +0 -0
  61. package/node_modules/@chati/core/native/process-info-pipe.c +91 -0
  62. package/node_modules/@chati/core/package.json +2 -1
  63. package/node_modules/@chati/core/scripts/build-native.js +36 -0
  64. package/node_modules/@chati/core/src/darwin-browser-proof.js +87 -0
  65. package/node_modules/@chati/core/src/darwin-host-boot.js +58 -0
  66. package/node_modules/@chati/core/src/darwin-process.js +79 -0
  67. package/node_modules/@chati/core/src/darwin-supervisor-channels.js +82 -0
  68. package/node_modules/@chati/core/src/darwin-terminal-receipt.js +111 -0
  69. package/node_modules/@chati/core/src/descriptor-fs.js +222 -0
  70. package/node_modules/@chati/core/src/execution-gate.js +8 -0
  71. package/node_modules/@chati/core/src/host-boot-marker.js +45 -0
  72. package/node_modules/@chati/core/src/index.js +18 -0
  73. package/node_modules/@chati/core/src/linux-host-boot.js +218 -0
  74. package/node_modules/@chati/core/src/linux-process.js +133 -0
  75. package/node_modules/@chati/planning/src/browser-verification-plan.js +71 -0
  76. package/node_modules/@chati/planning/src/index.js +33 -0
  77. package/node_modules/@chati/planning/src/planning-material.js +64 -0
  78. package/node_modules/@chati/planning/src/verification-plan.js +57 -0
  79. package/node_modules/@chati/rail/package.json +1 -0
  80. package/node_modules/@chati/rail/src/execution-restoration.js +92 -0
  81. package/node_modules/@chati/rail/src/index.js +812 -167
  82. package/node_modules/@chati/rail/src/provider-processing.js +118 -0
  83. package/node_modules/@chati/rail/src/terminal-containment.js +23 -0
  84. package/node_modules/@chati/release-lane/src/index.js +40 -43
  85. package/node_modules/@chati/review-council/src/index.js +223 -0
  86. package/node_modules/@chati/tracking-clickup/src/index.js +67 -58
  87. package/package-artifact-manifest.json +1 -1
  88. package/package-artifact-manifest.sig +1 -1
  89. package/package.json +3 -2
  90. package/scripts/verify-real-harness-e2e.js +20 -8
  91. package/src/installer/core.js +147 -107
  92. package/src/installer/package-artifact.js +7 -0
  93. package/src/installer/preflight.js +13 -0
  94. package/src/installer/provider-overlay.js +1 -2
  95. package/src/installer/templates.js +58 -0
  96. package/src/installer-v2/catalog-client.js +31 -1
  97. package/src/installer-v2/clickup-connection-storage.js +92 -0
  98. package/src/installer-v2/clickup-mcp-client.js +155 -0
  99. package/src/installer-v2/clickup-oauth-loopback.js +96 -0
  100. package/src/installer-v2/clickup-oauth.js +233 -0
  101. package/src/installer-v2/index.js +141 -15
  102. package/src/installer-v2/installation-authority.js +98 -4
  103. package/src/installer-v2/provider-executable.js +117 -12
  104. package/src/installer-v2/wizard-installation.js +11 -0
  105. package/src/orchestrator/agent-selector.js +39 -1
  106. package/src/orchestrator/browser-artifact-store.js +131 -0
  107. package/src/orchestrator/cli.js +762 -205
  108. package/src/orchestrator/clickup-mcp-observer.js +226 -0
  109. package/src/orchestrator/clickup-runtime.js +45 -24
  110. package/src/orchestrator/council-gate-runtime.js +903 -0
  111. package/src/orchestrator/evidence-authority-enrollment.js +75 -0
  112. package/src/orchestrator/evidence-authority-request.js +46 -0
  113. package/src/orchestrator/handoff-engine.js +4 -11
  114. package/src/orchestrator/pipeline-manager.js +42 -4
  115. package/src/orchestrator/planning-contract-revision.js +711 -0
  116. package/src/orchestrator/planning-reconciliation.js +302 -0
  117. package/src/orchestrator/planning-runtime.js +476 -29
  118. package/src/orchestrator/rail-adjudication-evidence.js +15 -24
  119. package/src/orchestrator/rail-human-effort-authority.js +46 -0
  120. package/src/orchestrator/rail-human-effort.js +149 -0
  121. package/src/orchestrator/rail-runtime.js +291 -47
  122. package/src/orchestrator/rail-verification.js +278 -0
  123. package/src/orchestrator/review-runtime.js +37 -2
  124. package/src/orchestrator/runtime-installation-v2.js +143 -24
  125. package/src/orchestrator/session-manager.js +1457 -29
  126. package/src/tasks/handoff.js +57 -33
  127. package/src/terminal/adapters/claude-adapter.js +3 -0
  128. package/src/terminal/adapters/codex-adapter.js +13 -0
  129. package/src/terminal/adapters/grok-adapter.js +1 -0
  130. package/src/terminal/agent-attempt-budget.js +35 -0
  131. package/src/terminal/browser-endpoint-proxy.js +174 -0
  132. package/src/terminal/browser-observer-driver.js +94 -0
  133. package/src/terminal/browser-observer-supervisor.js +73 -0
  134. package/src/terminal/browser-observer.js +95 -0
  135. package/src/terminal/browser-process-facts.js +75 -0
  136. package/src/terminal/browser-runtime.js +131 -0
  137. package/src/terminal/darwin-authority-policy.js +213 -0
  138. package/src/terminal/darwin-browser-application.js +116 -0
  139. package/src/terminal/darwin-browser-observer.js +57 -0
  140. package/src/terminal/darwin-execution.js +185 -0
  141. package/src/terminal/executable-snapshot.js +202 -0
  142. package/src/terminal/handoff-parser.js +48 -2
  143. package/src/terminal/isolation.js +14 -13
  144. package/src/terminal/prompt-builder.js +176 -15
  145. package/src/terminal/provider-preflight.js +47 -27
  146. package/src/terminal/rail-execution-worktree.js +22 -6
  147. package/src/terminal/rail-process-observer.js +10 -0
  148. package/src/terminal/rail-prompts.js +21 -3
  149. package/src/terminal/rail-readonly-workspace.js +54 -5
  150. package/src/terminal/rail-worktree-checkpoint.js +315 -0
  151. package/src/terminal/rail-worktree-restoration.js +485 -0
  152. package/src/terminal/run-agent.js +164 -42
  153. package/src/terminal/run-council-gate.js +338 -0
  154. package/src/terminal/run-planning-reconciliation.js +49 -0
  155. package/src/terminal/run-rail-adjudication.js +29 -7
  156. package/src/terminal/run-rail-recovery.js +97 -0
  157. package/src/terminal/run-rail-review.js +36 -15
  158. package/src/terminal/run-rail-rework.js +26 -15
  159. package/src/terminal/run-rail-task.js +22 -9
  160. package/src/terminal/runtime-native-helper.js +85 -0
  161. package/src/terminal/spawner.js +899 -142
  162. package/src/terminal/verification-process-facts.js +49 -0
  163. package/src/upgrade/entrypoints.js +68 -0
  164. package/src/upgrade/generation.js +194 -0
  165. package/src/upgrade/maintenance.js +79 -0
  166. package/src/upgrade/paths.js +81 -0
  167. package/src/upgrade/upgrade.js +267 -0
  168. package/src/utils/execution-role.js +15 -0
  169. package/src/utils/handoff-paths.js +59 -0
  170. package/src/wizard/i18n.js +1 -0
  171. package/src/wizard/index.js +17 -5
  172. package/src/wizard/questions.js +24 -12
@@ -3,19 +3,23 @@
3
3
  *
4
4
  * It creates one provider-neutral installation artifact, inspects a target for
5
5
  * legacy v1 markers, and returns a migration/rollback plan. Artifact writes
6
- * are explicit through installV2 or authorized migrateV2. It never invokes a
7
- * provider CLI or reads credentials.
6
+ * are explicit through installV2 or authorized migrateV2. Internal writes
7
+ * require the existing machine-license entitlement check. It never invokes a
8
+ * provider CLI or reads provider credentials.
8
9
  */
9
10
  import { existsSync } from 'node:fs';
10
11
  import { join, relative, resolve, sep } from 'node:path';
11
12
  import { canonicalize, canonicalSerialize, sha256, ContractError } from '@chati/core';
12
13
  import { validateInstallation } from '@chati/provider-registry';
14
+ import { selectCouncilReviewers } from '@chati/review-council';
13
15
  import {
14
16
  persistImmutableInstallationFile, readSafeInstallationStorageFile,
15
17
  replaceInstallationStorageFile, verifyInstallationCatalogProvenance,
16
18
  } from './catalog-client.js';
17
19
  import { authorizeInstallationArtifact } from './installation-authority.js';
18
20
  import { validateProviderExecutableBinding } from './provider-executable.js';
21
+ import { requireLicenseEntitlement } from '../license/client.js';
22
+ import { withUpgradeMaintenance } from '../upgrade/maintenance.js';
19
23
 
20
24
  export const INSTALLATION_SCHEMA_VERSION = 2;
21
25
  export const INSTALLATION_ARTIFACT_PATH = '.chati/v2/installation.json';
@@ -56,10 +60,20 @@ const ARTIFACT_KEYS = new Set([
56
60
  ]);
57
61
  const INSTALLATION_KEYS = new Set([
58
62
  'installation_id', 'profile', 'enabled_providers', 'capability_snapshot_ref',
63
+ 'quality_council',
59
64
  // Read-only compatibility for early v2 artifacts. Current installers never
60
65
  // emit these fields and runtime routing does not consume them.
61
66
  'policy_ref', 'primary_binding', 'primary_harness', 'external_integrations',
62
67
  ]);
68
+ const QUALITY_COUNCIL_KEYS = new Set([
69
+ 'mode', 'stages', 'min_members', 'max_members',
70
+ 'require_distinct_providers', 'blocking_severities',
71
+ ]);
72
+ const QUALITY_COUNCIL_STAGES = new Set(['discovery', 'planning', 'execution-design']);
73
+ // Artifacts created before this contract was introduced can still be loaded
74
+ // read-only. A current installer must never mint one: a caller-controlled
75
+ // clock is useful for deterministic artifacts, but is not evidence that an
76
+ // artifact really predates this security contract.
63
77
  const BINDING_KEYS = new Set([
64
78
  'provider_id', 'harness_id', 'allowed_models', 'allowed_reasoning_configurations',
65
79
  'executable_path', 'executable_sha256', 'supporting_executables',
@@ -73,6 +87,9 @@ const MODEL_KEYS = new Set([
73
87
  function assertClosedArtifactSchema(artifact) {
74
88
  assertExactKeys(artifact, ARTIFACT_KEYS, 'artifact');
75
89
  assertExactKeys(artifact.installation, INSTALLATION_KEYS, 'installation');
90
+ if (artifact.installation.quality_council !== undefined) {
91
+ assertExactKeys(artifact.installation.quality_council, QUALITY_COUNCIL_KEYS, 'quality council policy');
92
+ }
76
93
  for (const binding of artifact.installation.enabled_providers ?? []) {
77
94
  assertExactKeys(binding, BINDING_KEYS, 'enabled provider binding');
78
95
  }
@@ -92,6 +109,44 @@ function assertClosedArtifactSchema(artifact) {
92
109
  assertExactKeys(artifact.brain_read_capability, brainKeys, 'brain_read_capability');
93
110
  }
94
111
 
112
+ function validateQualityCouncilConfiguration(configuration, { allowVerifiedLegacyArtifact = false } = {}) {
113
+ if (configuration === undefined) {
114
+ // Published releases up to 4.5.28 wrote v2 artifacts without a council
115
+ // policy whatever their date. Read paths (doctor, upgrade, runtime load)
116
+ // keep those verified artifacts inspectable and runnable; the runtime
117
+ // then reports COUNCIL_POLICY_NOT_CONFIGURED. Every creation and
118
+ // authorization path stays unconditional, so a new artifact can never
119
+ // omit the policy.
120
+ if (allowVerifiedLegacyArtifact) return;
121
+ throw new ContractError(
122
+ 'QUALITY_COUNCIL_REQUIRED',
123
+ 'current v2 installations require a complete quality_council policy; only verified pre-existing artifacts may omit it',
124
+ );
125
+ }
126
+ assertExactKeys(configuration, QUALITY_COUNCIL_KEYS, 'quality council policy');
127
+ if (!['autonomous', 'guided'].includes(configuration.mode)) {
128
+ throw new ContractError('INVALID_COUNCIL_POLICY', 'quality_council.mode must be autonomous or guided');
129
+ }
130
+ if (!Array.isArray(configuration.stages) || configuration.stages.length !== QUALITY_COUNCIL_STAGES.size
131
+ || !configuration.stages.every((stage) => QUALITY_COUNCIL_STAGES.has(stage))
132
+ || new Set(configuration.stages).size !== QUALITY_COUNCIL_STAGES.size) {
133
+ throw new ContractError('INVALID_COUNCIL_POLICY', 'quality_council.stages must contain exactly discovery, planning and execution-design');
134
+ }
135
+ if (!Number.isInteger(configuration.min_members) || configuration.min_members < 2
136
+ || !Number.isInteger(configuration.max_members) || configuration.max_members < configuration.min_members
137
+ || configuration.max_members > 3) {
138
+ throw new ContractError('INVALID_COUNCIL_POLICY', 'quality_council member bounds must be between 2 and 3');
139
+ }
140
+ if (configuration.require_distinct_providers !== true) {
141
+ throw new ContractError('INVALID_COUNCIL_POLICY', 'quality_council requires distinct providers');
142
+ }
143
+ if (!Array.isArray(configuration.blocking_severities) || configuration.blocking_severities.length !== 2
144
+ || !configuration.blocking_severities.every((severity) => ['critical', 'high'].includes(severity))
145
+ || new Set(configuration.blocking_severities).size !== 2) {
146
+ throw new ContractError('INVALID_COUNCIL_POLICY', 'quality_council blocking severities must contain exactly critical and high');
147
+ }
148
+ }
149
+
95
150
  function readClock(clock) {
96
151
  if (typeof clock !== 'function') throw new ContractError('MISSING_CLOCK', 'a deterministic clock function is required');
97
152
  const result = clock();
@@ -147,6 +202,10 @@ function artifactCore(input, createdAt) {
147
202
  assertObject(input.capability_snapshot, 'INVALID_CATALOG', 'capability_snapshot');
148
203
  assertString(input.project_id, 'INVALID_INSTALLATION', 'project_id');
149
204
  validateInstallationExecutableBindings(input.installation);
205
+ // This is the creation boundary. Never derive a legacy exception from the
206
+ // supplied clock here, because install/reconfigure subsequently authorizes
207
+ // this exact artifact on the local host.
208
+ validateQualityCouncilConfiguration(input.installation.quality_council);
150
209
  const installation = validateInstallation(input.installation, input.capability_snapshot, { clock: () => createdAt });
151
210
  const brain_read_capability = normalizeBrainReadCapability(input.brain_read_capability);
152
211
  const managed_paths = (input.managed_paths ?? [INSTALLATION_ARTIFACT_PATH]).map(ensureRelativePath);
@@ -212,6 +271,10 @@ export function doctorV2(artifact, { clock = () => new Date() } = {}) {
212
271
  assertString(installation_artifact_id, 'INVALID_ARTIFACT', 'installation_artifact_id');
213
272
  if (sha256(core) !== digest) throw new ContractError('INSTALLATION_ARTIFACT_DRIFT', 'installation artifact digest does not match its content');
214
273
  validateInstallation(artifact.installation, artifact.capability_snapshot, { clock: () => readClock(clock) });
274
+ // The runtime additionally requires a valid pre-existing host receipt.
275
+ // doctor itself is read-only and therefore keeps old on-disk artifacts
276
+ // inspectable, while every current creation path above is unconditional.
277
+ validateQualityCouncilConfiguration(artifact.installation.quality_council, { allowVerifiedLegacyArtifact: true });
215
278
  validateInstallationExecutableBindings(artifact.installation);
216
279
  if (artifact.capability_snapshot.snapshot_id.startsWith('wizard-catalog-')
217
280
  && !artifact.capability_snapshot.source_catalog_ref) {
@@ -296,15 +359,61 @@ export function dryRunV2(input, { projectDir, clock = () => new Date(), exists =
296
359
  .map(({ provider_id, harness_id }) => ({ code: 'V1_BINDING_NOT_SELECTED', provider_id, harness_id, message: 'legacy marker is not a selected v2 binding' }));
297
360
  const pathOutsideProject = artifact.managed_paths.some((managedPath) => relative(resolve(projectDir), resolve(projectDir, managedPath)).split(sep).includes('..'));
298
361
  if (pathOutsideProject) throw new ContractError('INVALID_MANAGED_PATH', 'managed path resolves outside project');
299
- return Object.freeze(canonicalize({ schema_version: 1, mode: 'dry-run', writes_performed: false, artifact, doctor, detector, backup_plan, rollback_plan, incompatibilities }));
362
+ let council_readiness;
363
+ try {
364
+ const selection = assertExecutableV2Installation(artifact, { clock });
365
+ council_readiness = { ready: true, council_member_count: selection.member_count };
366
+ } catch (error) {
367
+ council_readiness = { ready: false, code: error.code || 'COUNCIL_READINESS_FAILED', message: error.message };
368
+ }
369
+ return Object.freeze(canonicalize({ schema_version: 1, mode: 'dry-run', writes_performed: false, artifact, doctor, detector, backup_plan, rollback_plan, incompatibilities, council_readiness }));
300
370
  }
301
371
 
302
- /** Explicit v2 installation. It writes only the provider-neutral artifact path. */
303
- export function installV2(input, {
304
- projectDir, clock = () => new Date(), exists = existsSync, authorityDirectory,
305
- } = {}) {
306
- const report = dryRunV2(input, { projectDir, clock, exists });
372
+ /**
373
+ * Read-only operational precondition for new writes, shared with the wizard
374
+ * before it installs framework files. Historical guided artifacts stay readable
375
+ * but require explicit reconfiguration, never an implicit provider addition.
376
+ * This proves council eligibility, not CLI authentication or end-to-end health.
377
+ */
378
+ export function assertExecutableV2Installation(artifact, { clock = () => new Date() } = {}) {
379
+ if (artifact?.installation?.quality_council?.mode !== 'autonomous') {
380
+ throw new ContractError('GUIDED_COUNCIL_REQUIRED',
381
+ 'installation requires an autonomous council with at least two distinct selected providers capable of review; explicitly reconfigure guided installations');
382
+ }
383
+ return selectCouncilReviewers({
384
+ installation: artifact.installation,
385
+ snapshot: artifact.capability_snapshot,
386
+ policy: artifact.installation.quality_council,
387
+ clock,
388
+ });
389
+ }
390
+
391
+ async function authorizeV2Write(report, { projectDir, clock, licenseValidator }) {
307
392
  verifyInstallationCatalogProvenance(projectDir, report.artifact.capability_snapshot, { clock });
393
+ assertExecutableV2Installation(report.artifact, { clock });
394
+ if (report.artifact.installation.profile === 'authorized-internal') {
395
+ // A local profile/cache is not entitlement. Await the same authoritative
396
+ // check as the wizard before backups, host signatures or artifact writes.
397
+ await requireLicenseEntitlement('internal', { validate: licenseValidator });
398
+ verifyInstallationCatalogProvenance(projectDir, report.artifact.capability_snapshot, { clock });
399
+ }
400
+ }
401
+
402
+ async function withAuthorizedV2Write(report, options, action) {
403
+ // Read-only preflight precedes even maintenance bookkeeping: denied Internal
404
+ // authorization must leave both the project and host authority unchanged.
405
+ await authorizeV2Write(report, options);
406
+ return withUpgradeMaintenance(options.projectDir, async () => {
407
+ // Provenance may have changed during the authorization await. No backup,
408
+ // signature or artifact write is permitted before this check and the lease.
409
+ verifyInstallationCatalogProvenance(options.projectDir, report.artifact.capability_snapshot, { clock: options.clock });
410
+ assertExecutableV2Installation(report.artifact, { clock: options.clock });
411
+ return action();
412
+ }, { createState: true });
413
+ }
414
+
415
+ /** Private persistence primitive, reached only after authorizeV2Write. */
416
+ function persistAuthorizedV2Report(report, { projectDir, authorityDirectory }) {
308
417
  const artifactPath = join(projectDir, INSTALLATION_ARTIFACT_PATH);
309
418
  const serialized = `${canonicalSerialize(report.artifact)}\n`;
310
419
  const existing = readSafeInstallationStorageFile(artifactPath, { allowMissing: true });
@@ -330,19 +439,32 @@ export function installV2(input, {
330
439
  }));
331
440
  }
332
441
 
442
+ /** Explicit v2 installation, including Internal entitlement before persistence. */
443
+ export async function installV2(input, {
444
+ projectDir, clock = () => new Date(), exists = existsSync, authorityDirectory, licenseValidator,
445
+ } = {}) {
446
+ const report = dryRunV2(input, { projectDir, clock, exists });
447
+ return withAuthorizedV2Write(report, { projectDir, clock, licenseValidator },
448
+ () => persistAuthorizedV2Report(report, { projectDir, authorityDirectory }));
449
+ }
450
+
333
451
  /**
334
452
  * Explicitly replaces an existing v2 artifact after preserving its exact
335
453
  * bytes in the project-local backup store. Provider-specific files are never
336
454
  * read or rewritten by this operation.
337
455
  */
338
- export function reconfigureV2(input, {
339
- projectDir, clock = () => new Date(), exists = existsSync, authorityDirectory,
456
+ export async function reconfigureV2(input, {
457
+ projectDir, clock = () => new Date(), exists = existsSync, authorityDirectory, licenseValidator,
340
458
  } = {}) {
341
459
  const report = dryRunV2(input, { projectDir, clock, exists });
342
- verifyInstallationCatalogProvenance(projectDir, report.artifact.capability_snapshot, { clock });
460
+ return withAuthorizedV2Write(report, { projectDir, clock, licenseValidator }, () =>
461
+ reconfigureAuthorizedV2Report(report, { projectDir, authorityDirectory }));
462
+ }
463
+
464
+ function reconfigureAuthorizedV2Report(report, { projectDir, authorityDirectory }) {
343
465
  const artifactPath = join(projectDir, INSTALLATION_ARTIFACT_PATH);
344
466
  const existing = readSafeInstallationStorageFile(artifactPath, { allowMissing: true });
345
- if (existing === null) return installV2(input, { projectDir, clock, exists, authorityDirectory });
467
+ if (existing === null) return persistAuthorizedV2Report(report, { projectDir, authorityDirectory });
346
468
  let existingArtifact;
347
469
  try { existingArtifact = JSON.parse(existing.content); } catch (error) {
348
470
  throw new ContractError('EXISTING_INSTALLATION_CORRUPT', `existing v2 artifact is not valid JSON: ${error.message}`);
@@ -374,14 +496,18 @@ export function reconfigureV2(input, {
374
496
  * legacy markers, writes the provider-neutral artifact, and never rewrites a
375
497
  * provider-specific configuration file.
376
498
  */
377
- export function migrateV2(input, {
378
- projectDir, authorize = false, clock = () => new Date(), exists = existsSync, authorityDirectory,
499
+ export async function migrateV2(input, {
500
+ projectDir, authorize = false, clock = () => new Date(), exists = existsSync, authorityDirectory, licenseValidator,
379
501
  } = {}) {
380
502
  const preview = dryRunV2(input, { projectDir, clock, exists });
381
503
  if (authorize !== true) {
382
504
  return Object.freeze(canonicalize({ ...preview, mode: 'migration-preview', writes_performed: false, requires_explicit_authorization: true }));
383
505
  }
506
+ return withAuthorizedV2Write(preview, { projectDir, clock, licenseValidator }, () =>
507
+ migrateAuthorizedV2Report(preview, { projectDir, authorityDirectory }));
508
+ }
384
509
 
510
+ function migrateAuthorizedV2Report(preview, { projectDir, authorityDirectory }) {
385
511
  const artifactPath = join(projectDir, INSTALLATION_ARTIFACT_PATH);
386
512
  const existingArtifact = readSafeInstallationStorageFile(artifactPath, { allowMissing: true });
387
513
  if (existingArtifact !== null) {
@@ -403,7 +529,7 @@ export function migrateV2(input, {
403
529
  conflictCode: 'MIGRATION_BACKUP_CONFLICT', label: 'legacy migration backup',
404
530
  })) backed_up.push(entry);
405
531
  }
406
- const installed = installV2(input, { projectDir, clock, exists, authorityDirectory });
532
+ const installed = persistAuthorizedV2Report(preview, { projectDir, authorityDirectory });
407
533
  return Object.freeze(canonicalize({
408
534
  ...preview,
409
535
  mode: 'migrated',
@@ -3,13 +3,14 @@ import {
3
3
  sign as cryptoSign, verify as cryptoVerify,
4
4
  } from 'node:crypto';
5
5
  import { userInfo } from 'node:os';
6
- import { basename, dirname, join, relative, resolve } from 'node:path';
6
+ import { basename, dirname, join, relative, resolve, sep } from 'node:path';
7
7
  import { lstatSync, realpathSync } from 'node:fs';
8
8
  import { canonicalSerialize, ContractError } from '@chati/core';
9
9
  import {
10
10
  persistImmutableInstallationFile, readSafeInstallationStorageFile,
11
11
  } from './catalog-client.js';
12
12
 
13
+ export { projectBinding as installationAuthorityProjectBinding };
13
14
  export const INSTALLATION_AUTHORITY_RECEIPT_VERSION = 1;
14
15
  export const INSTALLATION_AUTHORITY_RECEIPT_TYPE = 'chati-installation-authority';
15
16
  export const INSTALLATION_AUTHORITY_DIRECTORY = '.chati/v2/authorities';
@@ -66,7 +67,7 @@ function assertPrivateKeyPermissions(source) {
66
67
  }
67
68
  }
68
69
 
69
- function readOrCreateAuthorityKeyPair(authorityDirectory) {
70
+ function readOrCreateAuthorityKeyPair(authorityDirectory, { create = true } = {}) {
70
71
  const root = authorityRoot(authorityDirectory);
71
72
  const privatePath = join(root, PRIVATE_KEY_FILENAME);
72
73
  const publicPath = installationAuthorityPublicKeyPath(authorityDirectory);
@@ -74,6 +75,7 @@ function readOrCreateAuthorityKeyPair(authorityDirectory) {
74
75
  allowMissing: true, label: 'installation authority private key', encoding: 'utf8', maxBytes: 64 * 1024,
75
76
  });
76
77
  if (privateSource === null) {
78
+ if (!create) fail('DELEGATED_AUTHORITY_IDENTITY_REQUIRED', 'create a public certification request before using the delegated authority');
77
79
  const generated = generateKeyPairSync('ed25519', {
78
80
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
79
81
  publicKeyEncoding: { type: 'spki', format: 'pem' },
@@ -204,7 +206,34 @@ function unsignedReceipt(receipt) {
204
206
  export function authorizeInstallationArtifact(projectDir, artifact, {
205
207
  authorityDirectory,
206
208
  } = {}) {
209
+ // A host receipt is minted only by a current explicit install or
210
+ // reconfigure action. It must not be usable to manufacture a "historical"
211
+ // policy-less installation by passing an old deterministic clock.
212
+ if (artifact?.installation?.quality_council === undefined) {
213
+ fail('QUALITY_COUNCIL_REQUIRED', 'current host authority refuses to authorize an installation without quality_council');
214
+ }
207
215
  const authority = readOrCreateAuthorityKeyPair(authorityDirectory);
216
+ const path = receiptPath(projectDir, artifact);
217
+ const existing = readSafeInstallationStorageFile(path, {
218
+ allowMissing: true, label: 'installation authority receipt', encoding: 'utf8', maxBytes: 256 * 1024,
219
+ });
220
+ if (existing !== null) {
221
+ let receipt;
222
+ try { receipt = JSON.parse(existing.content); }
223
+ catch { fail('INVALID_INSTALLATION_AUTHORITY_RECEIPT', 'installation authority receipt is not valid JSON'); }
224
+ try {
225
+ verifyInstallationArtifactAuthority(projectDir, artifact, { authorityDirectory });
226
+ } catch (error) {
227
+ if (error?.code === 'INSTALLATION_AUTHORITY_MISMATCH') {
228
+ fail('INSTALLATION_AUTHORITY_RECEIPT_CONFLICT', 'installation authority receipt exists for a different host authority');
229
+ }
230
+ throw error;
231
+ }
232
+ return Object.freeze({
233
+ receipt: Object.freeze(receipt),
234
+ receipt_path: relative(resolve(projectDir), path).replaceAll('\\', '/'),
235
+ });
236
+ }
208
237
  const payload = {
209
238
  schema_version: INSTALLATION_AUTHORITY_RECEIPT_VERSION,
210
239
  receipt_type: INSTALLATION_AUTHORITY_RECEIPT_TYPE,
@@ -213,13 +242,14 @@ export function authorizeInstallationArtifact(projectDir, artifact, {
213
242
  installation_artifact_id: artifact.installation_artifact_id,
214
243
  installation_digest: artifact.digest,
215
244
  artifact_created_at: artifact.created_at,
216
- issued_at: artifact.created_at,
245
+ // This is an actual authority issue time, never the artifact's
246
+ // caller-supplied deterministic timestamp.
247
+ issued_at: new Date().toISOString(),
217
248
  };
218
249
  const receipt = Object.freeze({
219
250
  ...payload,
220
251
  signature: cryptoSign(null, Buffer.from(canonicalSerialize(payload)), authority.privateKey).toString('base64'),
221
252
  });
222
- const path = receiptPath(projectDir, artifact);
223
253
  persistImmutableInstallationFile(path, `${canonicalSerialize(receipt)}\n`, {
224
254
  conflictCode: 'INSTALLATION_AUTHORITY_RECEIPT_CONFLICT',
225
255
  label: 'installation authority receipt', maxBytes: 256 * 1024,
@@ -282,6 +312,70 @@ export function installationAuthorityStateDirectory(authorityDirectory) {
282
312
  return authorityRoot(authorityDirectory);
283
313
  }
284
314
 
315
+ function delegatedAuthority({ projectDir, projectId, kind, authorityDirectory, create = false } = {}) {
316
+ if (!['rail-evidence', 'clickup-mcp'].includes(kind) || typeof projectId !== 'string' || !projectId.trim()) {
317
+ fail('INVALID_DELEGATED_AUTHORITY', 'delegated authority requires a project and a supported evidence kind');
318
+ }
319
+ const root = authorityRoot(authorityDirectory);
320
+ const project = realpathSync(resolve(projectDir));
321
+ if (root === project || root.startsWith(`${project}${sep}`) || project.startsWith(`${root}${sep}`)) {
322
+ fail('DELEGATED_AUTHORITY_PROJECT_OVERLAP', 'delegated private authority storage must not overlap the project');
323
+ }
324
+ const binding = projectBinding(project, projectId);
325
+ const bindingDigest = createHash('sha256').update(canonicalSerialize({ kind, binding })).digest('hex');
326
+ const delegatedRoot = join(root, 'evidence-issuers', bindingDigest);
327
+ const authority = readOrCreateAuthorityKeyPair(delegatedRoot, { create });
328
+ return { ...authority, bindingDigest,
329
+ issuer_id: `local-${kind}://${keyFingerprint(authority.publicKey)}/${bindingDigest}` };
330
+ }
331
+
332
+ /** A distinct delegated key, not an additional root of trust. Public output only. */
333
+ export function delegatedAuthorityPublicIdentity(options) {
334
+ const authority = delegatedAuthority(options);
335
+ return Object.freeze({
336
+ issuer_id: authority.issuer_id, project_binding_digest: authority.bindingDigest,
337
+ public_key: authority.publicKey.export({ type: 'spki', format: 'pem' }),
338
+ });
339
+ }
340
+
341
+ /** Internal issuer primitive. No CLI accepts arbitrary statements to sign. */
342
+ export function signDelegatedAuthorityStatement(statement, options) {
343
+ const authority = delegatedAuthority({ ...options, create: false });
344
+ return Object.freeze({ issuer_id: authority.issuer_id,
345
+ signature: cryptoSign(null, Buffer.from(canonicalSerialize(statement)), authority.privateKey).toString('base64') });
346
+ }
347
+
348
+ /** Internal ClickUp observer primitive, not a CLI accepting results to sign.
349
+ * The existing v2 MCP receipt verifier uses this ordered JSON wire format,
350
+ * whereas certification requests and RAIL statements use canonical JSON.
351
+ * Keep those domains distinct; never export or recreate the delegated key.
352
+ */
353
+ export function signDelegatedClickUpMcpPayload(payload, options) {
354
+ const fields = ['schema_version', 'issuer_id', 'kind', 'projection_id', 'clickup_ref',
355
+ 'idempotency_key', 'operation', 'delivery_attempt', 'delivery_attempted_at', 'observed_at', 'response_digest'];
356
+ const invalid = () => fail('INVALID_CLICKUP_SIGNING_PAYLOAD', 'ClickUp observation requires its exact v2 signing payload');
357
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)
358
+ || ![null, Object.prototype].includes(Object.getPrototypeOf(payload))
359
+ || Object.getOwnPropertySymbols(payload).length
360
+ || Object.keys(payload).sort().join(',') !== [...fields].sort().join(',')) invalid();
361
+ const descriptors = Object.getOwnPropertyDescriptors(payload);
362
+ if (fields.some((name) => !Object.hasOwn(descriptors[name], 'value'))) invalid();
363
+ // Reconstruct the verifier's exact order, independent of caller insertion order.
364
+ const statement = Object.fromEntries(fields.map((name) => [name, descriptors[name].value]));
365
+ if (statement.schema_version !== 2 || !['clickup_ack', 'clickup_lookup'].includes(statement.kind)
366
+ || !['create', 'update', 'close'].includes(statement.operation)
367
+ || !Number.isSafeInteger(statement.delivery_attempt) || statement.delivery_attempt < 1
368
+ || !fields.filter((name) => !['schema_version', 'delivery_attempt'].includes(name))
369
+ .every((name) => typeof statement[name] === 'string' && statement[name].length > 0 && statement[name].length <= 8192)
370
+ || !/^[a-f0-9]{64}$/.test(statement.response_digest)
371
+ || !Number.isFinite(Date.parse(statement.delivery_attempted_at)) || !Number.isFinite(Date.parse(statement.observed_at))
372
+ || (options?.kind !== undefined && options.kind !== 'clickup-mcp')) invalid();
373
+ const authority = delegatedAuthority({ ...options, kind: 'clickup-mcp', create: false });
374
+ if (statement.issuer_id !== authority.issuer_id) invalid();
375
+ return Object.freeze({ issuer_id: authority.issuer_id,
376
+ signature: cryptoSign(null, Buffer.from(JSON.stringify(statement)), authority.privateKey).toString('base64') });
377
+ }
378
+
285
379
  /** Sign a domain-separated payload with the persistent host authority. */
286
380
  export function signHostAuthorityPayload(payload, {
287
381
  domain,
@@ -3,6 +3,7 @@ import {
3
3
  closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpathSync,
4
4
  } from 'node:fs';
5
5
  import { basename, delimiter, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
6
+ import { homedir } from 'node:os';
6
7
 
7
8
  const MAX_PROVIDER_EXECUTABLE_BYTES = 1024 * 1024 * 1024;
8
9
  const PROVIDER_EXECUTABLE_DIGEST = /^[a-f0-9]{64}$/;
@@ -14,6 +15,11 @@ const CODEX_NATIVE_TARGETS = Object.freeze({
14
15
  'darwin/arm64': Object.freeze(['@openai/codex-darwin-arm64', 'aarch64-apple-darwin']),
15
16
  });
16
17
 
18
+ /** A sealed native Grok launch must retain its npm launcher's managed marker. */
19
+ export function sealedProviderRuntimeEnvironment(harnessId) {
20
+ return Object.freeze(harnessId === 'grok' ? { GROK_MANAGED_BY_NPM: '1' } : {});
21
+ }
22
+
17
23
  function fail(code, message) {
18
24
  throw Object.assign(new Error(message), { code });
19
25
  }
@@ -87,19 +93,106 @@ function codexNativeExecutable(wrapperPath, { platform = process.platform, arch
87
93
  return fail('PROVIDER_EXECUTABLE_NOT_FOUND', 'Codex wrapper does not resolve to its installed native executable');
88
94
  }
89
95
 
90
- function effectiveProviderExecutable(harnessId, resolvedCommand, options) {
91
- if (harnessId !== 'codex') return resolvedCommand;
96
+ function executablePrefix(path) {
92
97
  let descriptor;
93
- let prefix;
94
98
  try {
95
- descriptor = openSync(resolvedCommand, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
99
+ descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
100
+ assertExecutableStat(fstatSync(descriptor));
96
101
  const buffer = Buffer.alloc(64);
97
102
  const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
98
- prefix = buffer.subarray(0, bytes).toString('utf8');
103
+ return buffer.subarray(0, bytes);
104
+ } finally {
105
+ if (descriptor !== undefined) closeSync(descriptor);
106
+ }
107
+ }
108
+
109
+ function isNativeImage(prefix) {
110
+ return ['7f454c46', 'feedface', 'cefaedfe', 'feedfacf', 'cffaedfe', 'cafebabe', 'bebafeca', 'cafebabf', 'bfbafeca']
111
+ .includes(prefix.subarray(0, 4).toString('hex'));
112
+ }
113
+
114
+ function knownNpmLauncherRoot(harnessId, path) {
115
+ const expected = harnessId === 'claude' ? '@anthropic-ai/claude-code' : '@xai-official/grok';
116
+ const root = dirname(dirname(path));
117
+ if (basename(dirname(path)) !== 'bin'
118
+ || `${basename(dirname(root))}/${basename(root)}` !== expected) return null;
119
+ let descriptor;
120
+ try {
121
+ descriptor = openSync(join(root, 'package.json'), constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
122
+ const before = fstatSync(descriptor);
123
+ if (!before.isFile() || before.size > 64 * 1024) fail('PROVIDER_EXECUTABLE_BINDING_INVALID', 'provider package metadata must be a bounded regular file');
124
+ const bytes = Buffer.alloc((64 * 1024) + 1);
125
+ let offset = 0;
126
+ while (offset < bytes.length) {
127
+ const count = readSync(descriptor, bytes, offset, bytes.length - offset, null);
128
+ if (!count) break;
129
+ offset += count;
130
+ }
131
+ if (offset > 64 * 1024 || !sameSnapshot(snapshot(before), snapshot(fstatSync(descriptor)))) {
132
+ fail('PROVIDER_EXECUTABLE_DRIFT', 'provider package metadata changed during resolution');
133
+ }
134
+ const metadata = JSON.parse(bytes.subarray(0, offset).toString('utf8'));
135
+ if (metadata.name !== expected || typeof metadata.bin?.[harnessId] !== 'string'
136
+ || resolve(root, metadata.bin[harnessId]) !== path) {
137
+ fail('PROVIDER_EXECUTABLE_BINDING_INVALID', 'provider launcher does not match its installed package metadata');
138
+ }
139
+ return root;
140
+ } catch (error) {
141
+ if (error?.code?.startsWith('PROVIDER_EXECUTABLE_')) throw error;
142
+ return fail('PROVIDER_EXECUTABLE_BINDING_INVALID', `provider package metadata is unavailable or invalid: ${error.code || error.name}`);
99
143
  } finally {
100
144
  if (descriptor !== undefined) closeSync(descriptor);
101
145
  }
102
- return prefix.startsWith('#!') ? codexNativeExecutable(resolvedCommand, options) : resolvedCommand;
146
+ }
147
+
148
+ function installedNativeCandidate(path) {
149
+ let resolved;
150
+ try { resolved = realpathSync(path); }
151
+ catch (error) { if (error?.code === 'ENOENT') return null; throw error; }
152
+ if (!isNativeImage(executablePrefix(resolved))) {
153
+ fail('PROVIDER_EXECUTABLE_BINDING_INVALID', 'provider native image resolved to a script or unsupported executable format');
154
+ }
155
+ return resolved;
156
+ }
157
+
158
+ function packageNativeExecutable(harnessId, root, { platform, arch, grokHome, libc }) {
159
+ if (!['linux', 'darwin'].includes(platform) || !['x64', 'arm64'].includes(arch)) {
160
+ fail('PROVIDER_EXECUTABLE_UNSUPPORTED', `${harnessId} native binding is unsupported on ${platform}/${arch}`);
161
+ }
162
+ if (harnessId === 'grok') {
163
+ // Follow the npm launcher's existing canonical installation, without
164
+ // running its decompression/bootstrap or changing any global CLI state.
165
+ const canonical = installedNativeCandidate(join(grokHome, 'bin', 'grok'));
166
+ if (canonical) return canonical;
167
+ }
168
+ if (platform === 'linux' && !['glibc', 'musl'].includes(libc)) {
169
+ fail('PROVIDER_EXECUTABLE_UNSUPPORTED', 'native provider binding requires a supported Linux libc');
170
+ }
171
+ const musl = harnessId === 'claude' && platform === 'linux' && libc === 'musl';
172
+ const scope = harnessId === 'claude' ? '@anthropic-ai' : '@xai-official';
173
+ const packageName = `${harnessId === 'claude' ? 'claude-code' : 'grok'}-${platform}-${arch}${musl ? '-musl' : ''}`;
174
+ const entry = harnessId === 'claude' ? 'claude' : join('bin', 'grok');
175
+ for (const candidate of [join(root, 'node_modules', scope, packageName, entry), join(dirname(root), packageName, entry)]) {
176
+ const native = installedNativeCandidate(candidate);
177
+ if (native) return native;
178
+ }
179
+ return fail('PROVIDER_EXECUTABLE_NOT_FOUND', `${harnessId} has no installed native image; complete that CLI's own installation before retrying Chati`);
180
+ }
181
+
182
+ function effectiveProviderExecutable(harnessId, resolvedCommand, options) {
183
+ const prefix = executablePrefix(resolvedCommand);
184
+ if (isNativeImage(prefix)) return resolvedCommand;
185
+ if (harnessId === 'claude' || harnessId === 'grok') {
186
+ const packageRoot = knownNpmLauncherRoot(harnessId, resolvedCommand);
187
+ if (packageRoot) return packageNativeExecutable(harnessId, packageRoot, options);
188
+ }
189
+ // Resolving a multicall shell alias loses its invocation name when sealed
190
+ // descriptors are executed. Never silently bind the shared dispatcher.
191
+ if (![harnessId, `${harnessId}.js`, `${harnessId}.cjs`, `${harnessId}.mjs`, `${harnessId}.exe`].includes(basename(resolvedCommand))) {
192
+ fail('PROVIDER_EXECUTABLE_WRAPPER_UNSUPPORTED', `${harnessId} resolves to a non-native shared or renamed launcher; expose the installed provider executable in the installation PATH`);
193
+ }
194
+ return harnessId === 'codex' && prefix.toString('utf8').startsWith('#!')
195
+ ? codexNativeExecutable(resolvedCommand, options) : resolvedCommand;
103
196
  }
104
197
 
105
198
  function digestExecutablePath(executablePath) {
@@ -107,7 +200,7 @@ function digestExecutablePath(executablePath) {
107
200
  try {
108
201
  const pathStat = lstatSync(executablePath);
109
202
  if (pathStat.isSymbolicLink()) fail('PROVIDER_EXECUTABLE_BINDING_INVALID', 'provider executable binding may not end in a symlink');
110
- descriptor = openSync(executablePath, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
203
+ descriptor = openSync(executablePath, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
111
204
  const pinned = fstatSync(descriptor);
112
205
  if (pathStat.dev !== pinned.dev || pathStat.ino !== pinned.ino) {
113
206
  fail('PROVIDER_EXECUTABLE_DRIFT', 'provider executable changed before descriptor pinning');
@@ -133,6 +226,8 @@ export function resolveProviderExecutableBinding(harnessId, {
133
226
  pathEnv = process.env.PATH,
134
227
  platform = process.platform,
135
228
  arch = process.arch,
229
+ grokHome = process.env.GROK_HOME ?? join(homedir(), '.grok'),
230
+ libc = process.platform === 'linux' && !process.report?.getReport()?.header?.glibcVersionRuntime ? 'musl' : 'glibc',
136
231
  } = {}) {
137
232
  if (!['claude', 'codex', 'grok'].includes(harnessId)) {
138
233
  fail('PROVIDER_EXECUTABLE_UNSUPPORTED', `provider executable binding is unsupported for ${harnessId}`);
@@ -140,7 +235,7 @@ export function resolveProviderExecutableBinding(harnessId, {
140
235
  const executable_path = effectiveProviderExecutable(
141
236
  harnessId,
142
237
  resolveFromPath(harnessId, pathEnv),
143
- { platform, arch },
238
+ { platform, arch, grokHome, libc },
144
239
  );
145
240
  const digest = digestExecutablePath(executable_path);
146
241
  return Object.freeze({
@@ -201,13 +296,13 @@ function assertOutsideProject(executablePath, projectDir) {
201
296
  }
202
297
  }
203
298
 
204
- function openPinnedExecutable(reference, { projectDir } = {}) {
299
+ function openPinnedExecutable(reference, { projectDir, requireNativeImage = false } = {}) {
205
300
  assertOutsideProject(reference.executable_path, projectDir);
206
301
  let descriptor;
207
302
  try {
208
303
  const pathStat = lstatSync(reference.executable_path);
209
304
  if (pathStat.isSymbolicLink()) fail('PROVIDER_EXECUTABLE_BINDING_INVALID', 'provider executable binding may not end in a symlink');
210
- descriptor = openSync(reference.executable_path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0));
305
+ descriptor = openSync(reference.executable_path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
211
306
  const pinned = fstatSync(descriptor);
212
307
  if (pathStat.dev !== pinned.dev || pathStat.ino !== pinned.ino) {
213
308
  fail('PROVIDER_EXECUTABLE_DRIFT', 'provider executable changed before descriptor pinning');
@@ -216,6 +311,16 @@ function openPinnedExecutable(reference, { projectDir } = {}) {
216
311
  if (executable.sha256 !== reference.executable_sha256) {
217
312
  fail('PROVIDER_EXECUTABLE_DRIFT', 'provider executable digest differs from the sealed installation binding');
218
313
  }
314
+ if (requireNativeImage) {
315
+ const prefix = Buffer.alloc(4);
316
+ const count = readSync(descriptor, prefix, 0, prefix.length, 0);
317
+ if (!sameSnapshot(snapshot(fstatSync(descriptor)), executable.snapshot)) {
318
+ fail('PROVIDER_EXECUTABLE_DRIFT', 'verification interpreter changed during native-format validation');
319
+ }
320
+ if (count !== prefix.length || !isNativeImage(prefix)) {
321
+ fail('PROVIDER_EXECUTABLE_NATIVE_REQUIRED', 'verification checks require a pinned native interpreter, not a shebang wrapper');
322
+ }
323
+ }
219
324
  return Object.freeze({ descriptor, binding: reference, executable });
220
325
  } catch (error) {
221
326
  if (descriptor !== undefined) closeSync(descriptor);
@@ -224,9 +329,9 @@ function openPinnedExecutable(reference, { projectDir } = {}) {
224
329
  }
225
330
  }
226
331
 
227
- export function openPinnedProviderExecutable(binding, { harnessId, projectDir } = {}) {
332
+ export function openPinnedProviderExecutable(binding, { harnessId, projectDir, requireNativeImage = false } = {}) {
228
333
  const validated = validateProviderExecutableBinding(binding, { harnessId });
229
- return openPinnedExecutable(validated, { projectDir });
334
+ return openPinnedExecutable(validated, { projectDir, requireNativeImage });
230
335
  }
231
336
 
232
337
  export function openPinnedProviderSupportingExecutables(binding, { harnessId, projectDir } = {}) {
@@ -104,6 +104,17 @@ export function buildWizardV2InstallationInput({
104
104
  enabled_providers,
105
105
  capability_snapshot_ref: snapshotId,
106
106
  policy_ref: `policy://installation/${snapshotId}`,
107
+ // Preserve a descriptive guided artifact for previews and historical reads.
108
+ // New executable writes require autonomous mode and an eligible quorum;
109
+ // the installer enforces this without inventing an unselected reviewer.
110
+ quality_council: {
111
+ mode: providers.length >= 2 ? 'autonomous' : 'guided',
112
+ stages: ['discovery', 'planning', 'execution-design'],
113
+ min_members: 2,
114
+ max_members: 3,
115
+ require_distinct_providers: true,
116
+ blocking_severities: ['critical', 'high'],
117
+ },
107
118
  ...(installationMode === 'internal' ? { external_integrations: { clickup: 'required' } } : {}),
108
119
  };
109
120
  return Object.freeze({