@dsh-enhanced/plugin-control-plane 0.1.49 → 0.1.53

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 (52) hide show
  1. package/README.md +7 -3
  2. package/bin/dsh-local-release-adapter.js +3 -1
  3. package/lib/creation-capability-journal.d.ts.map +1 -1
  4. package/lib/creation-capability-journal.js +29 -5
  5. package/lib/creation-capability-journal.js.map +1 -1
  6. package/lib/creation-capability-runtime.d.ts.map +1 -1
  7. package/lib/creation-capability-runtime.js +15 -1
  8. package/lib/creation-capability-runtime.js.map +1 -1
  9. package/lib/creation-capability-types.d.ts +13 -0
  10. package/lib/creation-capability-types.d.ts.map +1 -1
  11. package/lib/native-source-build-worker.d.ts +4 -0
  12. package/lib/native-source-build-worker.d.ts.map +1 -0
  13. package/lib/native-source-build-worker.js +87 -0
  14. package/lib/native-source-build-worker.js.map +1 -0
  15. package/lib/native-source-build.d.ts +36 -0
  16. package/lib/native-source-build.d.ts.map +1 -0
  17. package/lib/native-source-build.js +143 -0
  18. package/lib/native-source-build.js.map +1 -0
  19. package/lib/native-source-pack.d.ts +11 -0
  20. package/lib/native-source-pack.d.ts.map +1 -0
  21. package/lib/native-source-pack.js +83 -0
  22. package/lib/native-source-pack.js.map +1 -0
  23. package/lib/runtime-observer-protocol.d.ts +3 -3
  24. package/lib/runtime-observer-protocol.js +5 -5
  25. package/lib/runtime-observer-protocol.js.map +1 -1
  26. package/lib/service.d.ts +1 -0
  27. package/lib/service.d.ts.map +1 -1
  28. package/lib/service.js +123 -61
  29. package/lib/service.js.map +1 -1
  30. package/lib/source-approval-authority.d.ts.map +1 -1
  31. package/lib/source-approval-authority.js +1 -0
  32. package/lib/source-approval-authority.js.map +1 -1
  33. package/lib/source-build.d.ts +26 -8
  34. package/lib/source-build.d.ts.map +1 -1
  35. package/lib/source-build.js +170 -51
  36. package/lib/source-build.js.map +1 -1
  37. package/lib/source-creation.js +1 -1
  38. package/lib/source-creation.js.map +1 -1
  39. package/lib/source-job-types.d.ts +9 -1
  40. package/lib/source-job-types.d.ts.map +1 -1
  41. package/lib/source-jobs.d.ts +3 -0
  42. package/lib/source-jobs.d.ts.map +1 -1
  43. package/lib/source-jobs.js +39 -4
  44. package/lib/source-jobs.js.map +1 -1
  45. package/lib/store.d.ts.map +1 -1
  46. package/lib/store.js +50 -5
  47. package/lib/store.js.map +1 -1
  48. package/lib/types.d.ts +14 -1
  49. package/lib/types.d.ts.map +1 -1
  50. package/lib/version.d.ts +1 -1
  51. package/lib/version.js +1 -1
  52. package/package.json +8 -7
package/lib/service.js CHANGED
@@ -26,6 +26,9 @@ import { adoptSourceRelease, validateSourceAdoptionConfig } from './source-adopt
26
26
  import { Ed25519ApprovalAuthority } from './approval.js';
27
27
  import { ControlPlaneStore, MODIFY_GENERATOR_DIGEST, controlPlaneDigest } from './store.js';
28
28
  import { runDockerPreparedChecks, validateSourceBuildConfig } from './source-build.js';
29
+ import { runNativePreparedChecks } from './native-source-build.js';
30
+ import { NativeMacVerifierRunner } from '@dsh-enhanced/assistant-isolation';
31
+ import { NATIVE_SOURCE_COMPILER } from './native-source-build-worker.js';
29
32
  import { awaitSourceSignal, inspectSourceContext, inspectSourceTargetsContext } from './source-context.js';
30
33
  import { inspectSourceRevisionContext, prepareRevisedPluginWorkspace, verifyRevisedPluginWorkspace } from './source-revision.js';
31
34
  import { inspectSourceCreationContext, prepareCreatedPluginWorkspace, validateSourceCreationFiles, verifyCreatedPluginWorkspace } from './source-creation.js';
@@ -133,6 +136,13 @@ export function normalizeControlPlaneConfig(input) {
133
136
  validateSourceApprovalClientConfig(config.sourceApprovals);
134
137
  if (config.sourceBuild !== undefined)
135
138
  validateSourceBuildConfig(config.sourceBuild);
139
+ if (config.sourceBuild !== undefined && 'backend' in config.sourceBuild) {
140
+ if (!config.sourceJobs?.allowedModes || config.sourceJobs.allowedModes.some(mode => mode === 'modify')
141
+ || config.sourceReleases !== undefined || config.sourceReleaseExecution !== undefined
142
+ || config.sourceAdoptions !== undefined || config.sourceApprovals !== undefined) {
143
+ throw new Error('plugin-control-plane: native source build only supports Host-scaffolded create/revise-created');
144
+ }
145
+ }
136
146
  if (config.sourceReleases !== undefined) {
137
147
  validateSourceReleaseClientConfig(config.sourceReleases);
138
148
  if (!config.sourceApprovals || config.sourceBuild?.versioning !== 'patch')
@@ -251,6 +261,7 @@ export class PluginControlPlaneService extends Service {
251
261
  liveRuntime;
252
262
  assertLiveRuntime;
253
263
  sourceRuntime;
264
+ nativeSourceRunner;
254
265
  sourceRuntimes = new Set();
255
266
  creationCapabilityRuntime;
256
267
  creationCapabilityRuntimes = new Set();
@@ -618,68 +629,109 @@ export class PluginControlPlaneService extends Service {
618
629
  throw error;
619
630
  }
620
631
  }
621
- const runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: this.config.sourceBuild, statePath: this.config.statePath, store: this.store,
622
- ports: {
623
- automations: {
624
- registerHostExecutor: executor => current('automations').registerHostExecutor(executor),
625
- reconcileSystem: request => current('automations').reconcileSystem(request),
626
- reconcileSystemExact: request => current('automations').reconcileSystemExact(request),
627
- pauseSystemOwned: request => current('automations').pauseSystemOwned(request),
628
- inspectSystemOwnedActivation: request => current('automations').inspectSystemOwnedActivation(request),
629
- inspectSystemOwned: request => current('automations').inspectSystemOwned(request),
630
- },
631
- delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
632
- }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
633
- assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
634
- ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
635
- verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
636
- ...(this.config.revisionRegressions ? { verifyPreparedRevisionRegression: (job, signal) => this.verifyPreparedRevisionRegression(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
637
- ...(this.config.creationVerifications ? { verifyPreparedCreation: (job, signal) => this.verifyPreparedCreation(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
638
- ...(capabilities ? { creationAdoptionEligible: (planId) => capabilities.eligible(planId),
639
- adoptVerifiedCreation: async (job, signal) => {
640
- if (!job.planId)
641
- throw new Error('created capability has no prepared source plan');
642
- await capabilities.adopt(job.planId, signal);
643
- } } : {}),
644
- ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
645
- if (!job.planId)
646
- throw new Error('source job has no prepared plan');
647
- await this.requestOwnerSourceApproval({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
648
- } } : {}),
649
- ...(this.config.sourceReleases ? { releasePrepared: async (job, signal) => {
650
- if (!job.planId)
651
- throw new Error('source job has no prepared plan');
652
- await this.requestOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
653
- } } : {}),
654
- ...(this.config.sourceReleaseExecution ? { releaseTimeoutMs: this.config.sourceReleaseExecution.timeoutMs,
655
- advanceReleased: async (job, signal) => {
656
- if (!job.planId)
657
- throw new Error('source job has no release plan');
658
- await this.advanceOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
659
- } } : {}),
660
- ...(this.config.sourceAdoptions ? { adoptionTimeoutMs: this.config.sourceAdoptions.timeoutMs,
661
- adoptReleased: async (job, signal, assertCurrent) => {
662
- if (!job.planId)
663
- throw new Error('source job has no released plan');
664
- await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
665
- } } : {}),
666
- trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent), });
632
+ const sourceBuild = this.config.sourceBuild;
633
+ let nativeRunner;
634
+ try {
635
+ nativeRunner = 'backend' in sourceBuild ? new NativeMacVerifierRunner({ backend: 'macos-seatbelt',
636
+ stateRoot: sourceBuild.stateRoot, authorityDigest: sourceBuild.authorityDigest,
637
+ runtime: sourceBuild.runtime, source: NATIVE_SOURCE_COMPILER, expiresAt: sourceBuild.expiresAt,
638
+ maxRuns: sourceBuild.maxRuns, maxTotalDurationMs: sourceBuild.maxTotalDurationMs,
639
+ maxDurationMs: sourceBuild.timeoutMs, maxOutputBytes: sourceBuild.outputBytes }) : undefined;
640
+ }
641
+ catch (error) {
642
+ unsubscribeTaskChanges?.();
643
+ if (capabilities) {
644
+ await capabilities.close();
645
+ this.creationCapabilityRuntimes.delete(capabilities);
646
+ }
647
+ throw error;
648
+ }
649
+ this.nativeSourceRunner = nativeRunner;
650
+ let runtime;
651
+ try {
652
+ runtime = new SourceJobRuntime({ config: this.config.sourceJobs, build: sourceBuild, statePath: this.config.statePath, store: this.store,
653
+ ports: {
654
+ automations: {
655
+ registerHostExecutor: executor => current('automations').registerHostExecutor(executor),
656
+ reconcileSystem: request => current('automations').reconcileSystem(request),
657
+ reconcileSystemExact: request => current('automations').reconcileSystemExact(request),
658
+ pauseSystemOwned: request => current('automations').pauseSystemOwned(request),
659
+ inspectSystemOwnedActivation: request => current('automations').inspectSystemOwnedActivation(request),
660
+ inspectSystemOwned: request => current('automations').inspectSystemOwned(request),
661
+ },
662
+ delivery: { validateOwnerRoute: request => current('delivery').validateOwnerRoute(request) },
663
+ }, withGapSourceFence: (gapId, owner, callback) => this.taskGaps.withCurrent(gapId, owner, callback),
664
+ assertGrowthRun: (run, gapId, owner, generation) => this.assertSourceGrowthRun(run, gapId, owner, generation),
665
+ ...(this.config.revisionVerifications ? { inspectRevisionParent: this.inspectRevisionParent,
666
+ verifyPreparedRevision: (job, signal) => this.verifyPreparedRevision(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
667
+ ...(this.config.revisionRegressions ? { verifyPreparedRevisionRegression: (job, signal) => this.verifyPreparedRevisionRegression(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
668
+ ...(this.config.creationVerifications ? { verifyPreparedCreation: (job, signal) => this.verifyPreparedCreation(job, signal, () => jobsCtx.get('assistantVerifier')) } : {}),
669
+ ...(capabilities ? { creationAdoptionEligible: (planId) => capabilities.eligible(planId),
670
+ adoptVerifiedCreation: async (job, signal) => {
671
+ if (!job.planId)
672
+ throw new Error('created capability has no prepared source plan');
673
+ await capabilities.adopt(job.planId, signal);
674
+ } } : {}),
675
+ ...(this.config.sourceApprovals ? { approvePrepared: async (job, signal) => {
676
+ if (!job.planId)
677
+ throw new Error('source job has no prepared plan');
678
+ await this.requestOwnerSourceApproval({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
679
+ } } : {}),
680
+ ...(this.config.sourceReleases ? { releasePrepared: async (job, signal) => {
681
+ if (!job.planId)
682
+ throw new Error('source job has no prepared plan');
683
+ await this.requestOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
684
+ } } : {}),
685
+ ...(this.config.sourceReleaseExecution ? { releaseTimeoutMs: this.config.sourceReleaseExecution.timeoutMs,
686
+ advanceReleased: async (job, signal) => {
687
+ if (!job.planId)
688
+ throw new Error('source job has no release plan');
689
+ await this.advanceOwnerSourceRelease({ planId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest });
690
+ } } : {}),
691
+ ...(this.config.sourceAdoptions ? { adoptionTimeoutMs: this.config.sourceAdoptions.timeoutMs,
692
+ adoptReleased: async (job, signal, assertCurrent) => {
693
+ if (!job.planId)
694
+ throw new Error('source job has no released plan');
695
+ await this.adoptOwnerSourceRelease({ sourcePlanId: job.planId, signal, expectedTrustDigest: job.intent.trustDigest, assertCurrent });
696
+ } } : {}),
697
+ trust: () => this.boundTrust(), prepare: (job, signal, assertCurrent) => this.prepareSourceJob(job, signal, assertCurrent),
698
+ ...(nativeRunner ? { inspectNativeQuiescence: (key) => nativeRunner.inspectQuiescence(key) } : {}),
699
+ });
700
+ }
701
+ catch (error) {
702
+ if (this.nativeSourceRunner === nativeRunner)
703
+ this.nativeSourceRunner = undefined;
704
+ await nativeRunner?.close();
705
+ unsubscribeTaskChanges?.();
706
+ if (capabilities) {
707
+ await capabilities.close();
708
+ this.creationCapabilityRuntimes.delete(capabilities);
709
+ }
710
+ throw error;
711
+ }
667
712
  this.sourceRuntimes.add(runtime);
668
713
  const close = async () => {
669
714
  unsubscribeTaskChanges?.();
670
715
  if (this.sourceRuntime === runtime)
671
716
  this.sourceRuntime = undefined;
717
+ let closeError;
672
718
  try {
673
719
  await runtime.close();
674
720
  }
675
- finally {
676
- this.sourceRuntimes.delete(runtime);
677
- if (capabilities) {
678
- if (this.creationCapabilityRuntime === capabilities)
679
- this.creationCapabilityRuntime = undefined;
680
- await capabilities.close();
681
- this.creationCapabilityRuntimes.delete(capabilities);
682
- }
721
+ catch (error) {
722
+ closeError = error;
723
+ }
724
+ if (this.nativeSourceRunner === nativeRunner)
725
+ this.nativeSourceRunner = undefined;
726
+ this.sourceRuntimes.delete(runtime);
727
+ if (this.creationCapabilityRuntime === capabilities)
728
+ this.creationCapabilityRuntime = undefined;
729
+ const results = await Promise.allSettled([nativeRunner?.close(), capabilities?.close()]);
730
+ if (capabilities)
731
+ this.creationCapabilityRuntimes.delete(capabilities);
732
+ const failures = results.filter((result) => result.status === 'rejected');
733
+ if (closeError !== undefined || failures.length) {
734
+ throw new AggregateError([...(closeError === undefined ? [] : [closeError]), ...failures.map(result => result.reason)], 'source runtime cleanup failed');
683
735
  }
684
736
  };
685
737
  try {
@@ -2013,7 +2065,8 @@ export class PluginControlPlaneService extends Service {
2013
2065
  if (!Number.isSafeInteger(ttlMs) || ttlMs < 900_000 || ttlMs > 86_400_000) {
2014
2066
  throw new ControlPlaneCliError('INVALID_ARGUMENT', 'ttlMs must be an integer within 900000..86400000');
2015
2067
  }
2016
- const maximumTimeoutMs = this.config.sourceBuild?.profile === 'repository' ? 1_800_000 : 240_000;
2068
+ const maximumTimeoutMs = this.config.sourceBuild !== undefined && !('backend' in this.config.sourceBuild)
2069
+ && ['repository', 'portable-repository'].includes(this.config.sourceBuild.profile ?? '') ? 1_800_000 : 240_000;
2017
2070
  const timeoutMs = input.timeoutMs ?? 180_000;
2018
2071
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs < 60_000 || timeoutMs > maximumTimeoutMs) {
2019
2072
  throw new ControlPlaneCliError('INVALID_ARGUMENT', `timeoutMs must be an integer within 60000..${maximumTimeoutMs}`);
@@ -2057,12 +2110,21 @@ export class PluginControlPlaneService extends Service {
2057
2110
  // Only owner-bound creation jobs retain the exact package for the
2058
2111
  // independent verifier. Ordinary source tools never receive these bytes.
2059
2112
  const capturePack = (creating || revising) && this.store.getOwnerTaskFailureReference(input.gapId) !== undefined;
2060
- const checked = await runDockerPreparedChecks({ config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2061
- worktree: isolated.worktree, baseCommit, name, scope: generated?.scope ?? [`plugins/${name}`], environment, signal,
2062
- assertCurrent, preparedAt: Date.now(), ...(creation === undefined ? {} : { creation }),
2063
- ...(revision === undefined ? {} : { revision: { binding: revision, parentSource: parent.source, parentCertificate: parent.certificate, files: input.files } }),
2064
- ...(capturePack ? { capturePack: true } : {}),
2065
- ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
2113
+ const common = { worktree: isolated.worktree, baseCommit, name, scope: generated?.scope ?? [`plugins/${name}`],
2114
+ environment, signal, assertCurrent, preparedAt: Date.now(), ...(creation === undefined ? {} : { creation }),
2115
+ ...(revision === undefined ? {} : { revision: { binding: revision, parentSource: parent.source,
2116
+ parentCertificate: parent.certificate, files: input.files } }), ...(capturePack ? { capturePack: true } : {}) };
2117
+ const checked = 'backend' in configured
2118
+ ? await (async () => {
2119
+ if ((!creating && !revising) || sourceJob === undefined || this.nativeSourceRunner === undefined
2120
+ || sourceJob.intent.nativeResource?.key !== sourceJob.id) {
2121
+ throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'native source preparation requires a durable Host-scaffolded creation job');
2122
+ }
2123
+ return runNativePreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2124
+ runner: this.nativeSourceRunner, sourceJob: { id: sourceJob.id } });
2125
+ })()
2126
+ : await runDockerPreparedChecks({ ...common, config: { ...configured, timeoutMs: Math.min(timeoutMs, configured.timeoutMs) },
2127
+ ...(sourceJob === undefined ? {} : { sourceJob: { id: sourceJob.id, containerName: sourceJob.intent.containerName } }) });
2066
2128
  await assertCurrent();
2067
2129
  if (capturePack && checked.packArtifact === undefined)
2068
2130
  throw new ControlPlaneCliError('SOURCE_BOUNDARY', 'prepared creation artifact was not captured');