gate-evm-tools 1.2.0 → 1.2.2
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.
- package/README.md +6 -2
- package/bin/cli.js +3 -0
- package/bin/delivery.cjs +11 -6
- package/bin/migration.js +38 -0
- package/dist/commands/prepare.d.ts.map +1 -1
- package/dist/commands/prepare.js +7 -1
- package/dist/commands/prepare.js.map +1 -1
- package/dist/core/BytecodeChecker.d.ts.map +1 -1
- package/dist/core/BytecodeChecker.js +27 -4
- package/dist/core/BytecodeChecker.js.map +1 -1
- package/dist/core/preparation/artifacts.d.ts.map +1 -1
- package/dist/core/preparation/artifacts.js +12 -5
- package/dist/core/preparation/artifacts.js.map +1 -1
- package/dist/core/preparation/index.d.ts +3 -0
- package/dist/core/preparation/index.d.ts.map +1 -1
- package/dist/core/preparation/index.js +9 -2
- package/dist/core/preparation/index.js.map +1 -1
- package/dist/core/preparation/zksync.d.ts.map +1 -1
- package/dist/core/preparation/zksync.js +5 -0
- package/dist/core/preparation/zksync.js.map +1 -1
- package/dist/core/validateAndGenerateCalldata.d.ts.map +1 -1
- package/dist/core/validateAndGenerateCalldata.js +3 -0
- package/dist/core/validateAndGenerateCalldata.js.map +1 -1
- package/dist/deployment-zksync/index.d.ts +40 -0
- package/dist/deployment-zksync/index.d.ts.map +1 -0
- package/dist/deployment-zksync/index.js +59 -0
- package/dist/deployment-zksync/index.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -12
- package/dist/index.js.map +1 -1
- package/dist/migration/index.d.ts +76 -0
- package/dist/migration/index.d.ts.map +1 -0
- package/dist/migration/index.js +284 -0
- package/dist/migration/index.js.map +1 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.d.ts.map +1 -1
- package/dist-cjs/core/BytecodeChecker.js +27 -4
- package/dist-cjs/core/BytecodeChecker.js.map +1 -1
- package/dist-cjs/core/preparation/artifacts.js +11 -4
- package/dist-cjs/core/preparation/artifacts.js.map +1 -1
- package/dist-cjs/core/preparation/index.js +47 -6
- package/dist-cjs/core/preparation/index.js.map +1 -1
- package/dist-cjs/core/preparation/zksync.js +5 -0
- package/dist-cjs/core/preparation/zksync.js.map +1 -1
- package/dist-cjs/deployment-zksync/index.js +67 -0
- package/dist-cjs/deployment-zksync/index.js.map +1 -0
- package/dist-cjs/migration/index.js +328 -0
- package/dist-cjs/migration/index.js.map +1 -0
- package/dist-runtime/delivery/aggregate-diagnostics.js +3 -0
- package/dist-runtime/delivery/aggregate-diagnostics.js.map +1 -1
- package/dist-runtime/delivery/aggregate-inputs.d.ts +32 -6
- package/dist-runtime/delivery/aggregate-inputs.js +7 -3
- package/dist-runtime/delivery/aggregate-inputs.js.map +1 -1
- package/dist-runtime/delivery/aggregate-worker.js +3 -3
- package/dist-runtime/delivery/aggregate-worker.js.map +1 -1
- package/dist-runtime/delivery/aggregate.js +2 -2
- package/dist-runtime/delivery/aggregate.js.map +1 -1
- package/dist-runtime/delivery/cli-host.d.ts +17 -0
- package/dist-runtime/delivery/cli-host.js +118 -0
- package/dist-runtime/delivery/cli-host.js.map +1 -0
- package/dist-runtime/delivery/cli.d.ts +114 -0
- package/dist-runtime/delivery/cli.js +228 -0
- package/dist-runtime/delivery/cli.js.map +1 -0
- package/dist-runtime/delivery/compiler-dependency-pins.js +30 -1
- package/dist-runtime/delivery/compiler-dependency-pins.js.map +1 -1
- package/dist-runtime/delivery/configuration-definition.js +152 -11
- package/dist-runtime/delivery/configuration-definition.js.map +1 -1
- package/dist-runtime/delivery/configuration-hre.js +1 -1
- package/dist-runtime/delivery/configuration-hre.js.map +1 -1
- package/dist-runtime/delivery/configuration.js +7 -4
- package/dist-runtime/delivery/configuration.js.map +1 -1
- package/dist-runtime/delivery/custom-format.d.ts +2 -1
- package/dist-runtime/delivery/custom-format.js +15 -2
- package/dist-runtime/delivery/custom-format.js.map +1 -1
- package/dist-runtime/delivery/custom.d.ts +4 -0
- package/dist-runtime/delivery/custom.js +21 -0
- package/dist-runtime/delivery/custom.js.map +1 -1
- package/dist-runtime/delivery/development-inputs.d.ts +23 -0
- package/dist-runtime/delivery/development-inputs.js +117 -0
- package/dist-runtime/delivery/development-inputs.js.map +1 -0
- package/dist-runtime/delivery/development.d.ts +1 -0
- package/dist-runtime/delivery/development.js +7 -1
- package/dist-runtime/delivery/development.js.map +1 -1
- package/dist-runtime/delivery/execution-records.d.ts +16 -0
- package/dist-runtime/delivery/execution-records.js +48 -0
- package/dist-runtime/delivery/execution-records.js.map +1 -0
- package/dist-runtime/delivery/existing-code.d.ts +3 -0
- package/dist-runtime/delivery/existing-code.js +20 -0
- package/dist-runtime/delivery/existing-code.js.map +1 -1
- package/dist-runtime/delivery/history-package.d.ts +2 -0
- package/dist-runtime/delivery/history-package.js +70 -0
- package/dist-runtime/delivery/history-package.js.map +1 -0
- package/dist-runtime/delivery/input-files.d.ts +2 -0
- package/dist-runtime/delivery/input-files.js +5 -0
- package/dist-runtime/delivery/input-files.js.map +1 -1
- package/dist-runtime/delivery/inputs.js +11 -6
- package/dist-runtime/delivery/inputs.js.map +1 -1
- package/dist-runtime/delivery/materialization.js +20 -1
- package/dist-runtime/delivery/materialization.js.map +1 -1
- package/dist-runtime/delivery/planning.d.ts +1 -0
- package/dist-runtime/delivery/planning.js +10 -2
- package/dist-runtime/delivery/planning.js.map +1 -1
- package/dist-runtime/delivery/production-finalization.js +3 -2
- package/dist-runtime/delivery/production-finalization.js.map +1 -1
- package/dist-runtime/delivery/production.d.ts +2 -0
- package/dist-runtime/delivery/production.js +5 -4
- package/dist-runtime/delivery/production.js.map +1 -1
- package/dist-runtime/delivery/project-files.js +6 -1
- package/dist-runtime/delivery/project-files.js.map +1 -1
- package/dist-runtime/delivery/project-rpc.d.ts +1 -1
- package/dist-runtime/delivery/project-rpc.js +10 -4
- package/dist-runtime/delivery/project-rpc.js.map +1 -1
- package/dist-runtime/delivery/protocol.d.ts +6 -4
- package/dist-runtime/delivery/protocol.js +16 -5
- package/dist-runtime/delivery/protocol.js.map +1 -1
- package/dist-runtime/delivery/query.d.ts +21 -0
- package/dist-runtime/delivery/query.js +58 -0
- package/dist-runtime/delivery/query.js.map +1 -0
- package/dist-runtime/delivery/release-graph.d.ts +17 -0
- package/dist-runtime/delivery/release-graph.js +23 -0
- package/dist-runtime/delivery/release-graph.js.map +1 -1
- package/dist-runtime/delivery/release-history-capture.js +26 -27
- package/dist-runtime/delivery/release-history-capture.js.map +1 -1
- package/dist-runtime/delivery/release-history.d.ts +2 -1
- package/dist-runtime/delivery/release-history.js +30 -52
- package/dist-runtime/delivery/release-history.js.map +1 -1
- package/dist-runtime/delivery/release.d.ts +22 -0
- package/dist-runtime/delivery/release.js +13 -3
- package/dist-runtime/delivery/release.js.map +1 -1
- package/dist-runtime/delivery/scripts.d.ts +116 -0
- package/dist-runtime/delivery/scripts.js +289 -0
- package/dist-runtime/delivery/scripts.js.map +1 -0
- package/dist-runtime/delivery/setup.d.ts +1 -1
- package/dist-runtime/delivery/setup.js +26 -36
- package/dist-runtime/delivery/setup.js.map +1 -1
- package/dist-runtime/delivery/source.d.ts +5 -2
- package/dist-runtime/delivery/source.js +4 -3
- package/dist-runtime/delivery/source.js.map +1 -1
- package/dist-runtime/delivery/standalone-source.d.ts +40 -0
- package/dist-runtime/delivery/standalone-source.js +54 -0
- package/dist-runtime/delivery/standalone-source.js.map +1 -0
- package/dist-runtime/runtime-identity.json +2 -2
- package/dist-runtime/workflow/lib/calls.js +21 -1
- package/dist-runtime/workflow/lib/calls.js.map +1 -1
- package/docs/configuration-runtime-observation.md +4 -2
- package/docs/delivery-custom-input-closure.md +2 -0
- package/docs/delivery-development.md +2 -0
- package/docs/delivery-inputs.md +2 -0
- package/docs/delivery-release.md +20 -2
- package/docs/delivery-setup.md +25 -20
- package/docs/delivery-source.md +4 -2
- package/docs/migration.md +108 -0
- package/docs/script-execution.md +85 -0
- package/package.json +42 -19
- package/runtime/delivery/aggregate-diagnostics.ts +2 -0
- package/runtime/delivery/aggregate-inputs.ts +6 -5
- package/runtime/delivery/aggregate-worker.ts +3 -3
- package/runtime/delivery/aggregate.ts +2 -2
- package/runtime/delivery/cli-host.ts +55 -0
- package/runtime/delivery/cli.ts +124 -0
- package/runtime/delivery/compiler-dependency-pins.ts +30 -1
- package/runtime/delivery/configuration-definition.ts +80 -9
- package/runtime/delivery/configuration-hre.ts +1 -1
- package/runtime/delivery/configuration.ts +7 -5
- package/runtime/delivery/custom-format.ts +11 -3
- package/runtime/delivery/custom.ts +13 -0
- package/runtime/delivery/development-inputs.ts +73 -0
- package/runtime/delivery/development.ts +4 -2
- package/runtime/delivery/execution-records.ts +31 -0
- package/runtime/delivery/existing-code.ts +12 -1
- package/runtime/delivery/history-package.ts +50 -0
- package/runtime/delivery/input-files.ts +5 -1
- package/runtime/delivery/inputs.ts +10 -6
- package/runtime/delivery/materialization.ts +8 -1
- package/runtime/delivery/planning.ts +10 -2
- package/runtime/delivery/production-finalization.ts +3 -2
- package/runtime/delivery/production.ts +5 -4
- package/runtime/delivery/project-files.ts +5 -1
- package/runtime/delivery/project-rpc.ts +10 -4
- package/runtime/delivery/protocol.ts +13 -5
- package/runtime/delivery/query.ts +34 -0
- package/runtime/delivery/release-graph.ts +17 -0
- package/runtime/delivery/release-history-capture.ts +21 -10
- package/runtime/delivery/release-history.ts +20 -19
- package/runtime/delivery/release.ts +7 -4
- package/runtime/delivery/scripts.ts +162 -0
- package/runtime/delivery/setup.ts +21 -31
- package/runtime/delivery/source.ts +7 -3
- package/runtime/delivery/standalone-source.ts +40 -0
- package/runtime/workflow/lib/calls.ts +19 -1
- package/dist-runtime/delivery/toolchain.d.ts +0 -3
- package/dist-runtime/delivery/toolchain.js +0 -52
- package/dist-runtime/delivery/toolchain.js.map +0 -1
- package/runtime/delivery/toolchain.ts +0 -48
|
@@ -59,6 +59,7 @@ export interface PlanningIdentityPort {
|
|
|
59
59
|
): Promise<{ chainId: string; blockNumber: number; blockHash: string }>;
|
|
60
60
|
}
|
|
61
61
|
export interface DeliveryRuntimeEvidence {
|
|
62
|
+
views?: unknown[];
|
|
62
63
|
/** Validated during this collection only; never hashed or compared as canonical replay content. */
|
|
63
64
|
pendingUupsReuse: { stepId: string; binding: PendingReuseBinding }[];
|
|
64
65
|
forkAudit: ForkAuditEvidence;
|
|
@@ -230,6 +231,7 @@ async function collectOwned(input:DeliveryPlanningOptions,staging:boolean):Promi
|
|
|
230
231
|
const callReviews = new Map<string, CalldataTransactionReview>();
|
|
231
232
|
const operationIds = new DeliveryOperationIdentities();
|
|
232
233
|
const references: DeliveryRuntimeEvidence["references"] = [];
|
|
234
|
+
const views:unknown[]=[];
|
|
233
235
|
const customBindings: CustomEvidence[] = [],
|
|
234
236
|
executed: WorkflowStep[] = [];
|
|
235
237
|
const session = sourceSteps.some((step) => step.kind === "custom")
|
|
@@ -241,6 +243,11 @@ async function collectOwned(input:DeliveryPlanningOptions,staging:boolean):Promi
|
|
|
241
243
|
params: context.targetParams,
|
|
242
244
|
target: context.target,
|
|
243
245
|
state: () => context.state.snapshot(),
|
|
246
|
+
query: async query => {
|
|
247
|
+
const request=(method:string,params:unknown[])=>context.runtime.hre.network.provider.request({method,params}) as Promise<any>;
|
|
248
|
+
const block:any=await request('eth_getBlockByNumber',['latest',false]);
|
|
249
|
+
return require('./query').readScriptContract(query,{request,chainId:header.chainId,block:{number:block.number,hash:block.hash},caller:header.operator,source:pinned});
|
|
250
|
+
},
|
|
244
251
|
}),
|
|
245
252
|
context.root,
|
|
246
253
|
context.taskDir
|
|
@@ -249,6 +256,7 @@ async function collectOwned(input:DeliveryPlanningOptions,staging:boolean):Promi
|
|
|
249
256
|
const pendingUupsReuse = context.pendingUupsReuse;
|
|
250
257
|
const stepContext: DeliveryStepContext = {
|
|
251
258
|
...context,
|
|
259
|
+
reporter:{event:(...args:any[])=>context.reporter.event(...args as [any,any]),result:(kind:any,data:any)=>{if(kind==='view')views.push(strictData(data));context.reporter.result(kind,data);}},
|
|
252
260
|
deploymentRecorder: undefined,
|
|
253
261
|
bindings,
|
|
254
262
|
preparations,
|
|
@@ -373,7 +381,7 @@ async function collectOwned(input:DeliveryPlanningOptions,staging:boolean):Promi
|
|
|
373
381
|
throw new Error("Replayed verification evidence differs");
|
|
374
382
|
session?.closeReads();
|
|
375
383
|
const interval=await audit.capture({transactions,completedTransactions:input.completedTransactions??[]});
|
|
376
|
-
const stagedSnapshot=deepFreeze({payload,calldata,preparations:prepared,verification:observations,runtimeEvidence:{pendingUupsReuse:pendingObservations,forkAudit:interval.snapshot,references}});
|
|
384
|
+
const stagedSnapshot=deepFreeze({payload,calldata,preparations:prepared,verification:observations,runtimeEvidence:{pendingUupsReuse:pendingObservations,forkAudit:interval.snapshot,references,...(views.length?{views}:{})}});
|
|
377
385
|
const invariantHash=semanticHash(stagedSnapshot);
|
|
378
386
|
let state:'staged'|'bound'|'failed'|'closed'='staged';
|
|
379
387
|
const open=()=>{if(state!=='staged')throw new Error('Staged collection closed or already bound');};
|
|
@@ -383,7 +391,7 @@ async function collectOwned(input:DeliveryPlanningOptions,staging:boolean):Promi
|
|
|
383
391
|
if(expected&&plan.planHash!==expected.planHash)throw new Error('Replayed delivery plan differs from expected plan');
|
|
384
392
|
const forkAudit=interval.bind(plan.planHash);
|
|
385
393
|
const result=deepFreeze(canonicalData({plan,calldata,preparations:prepared,verification:observations,
|
|
386
|
-
runtimeEvidence:{pendingUupsReuse:pendingObservations,forkAudit,references}}) as unknown as DeliveryCollection);
|
|
394
|
+
runtimeEvidence:{pendingUupsReuse:pendingObservations,forkAudit,references,...(views.length?{views}:{})}}) as unknown as DeliveryCollection);
|
|
387
395
|
if(collectionInvariantHash(result)!==invariantHash)throw new Error('Binding changed staged collection payload');
|
|
388
396
|
state='bound';return result;
|
|
389
397
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {interactionCoverageRegistry} from './existing-code';
|
|
1
2
|
/** Static production acceptance. No project module, HRE, compiler process or signing port is loaded. */
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import {getCreate2Address,Interface} from 'ethers';
|
|
@@ -78,7 +79,7 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
78
79
|
const deployed=[...changes.values()],unchangedAliases=new Set<string>();
|
|
79
80
|
const registrationContext={base,target:plan.target,deploymentId:plan.deploymentId,executionId:plan.executionId,recordPath:recordPath(ready),updatedAt:new Date(Number(BigInt(anchor.timestamp))*1000).toISOString(),changes:deployed,preparations};
|
|
80
81
|
// Apply the same validated upgrade replacement before resolving code coverage.
|
|
81
|
-
const candidate=mergeDevelopmentRegistration({...base,...registrationContext}),registry=candidate.registry[plan.target],metadata=candidate.metadata.networks[plan.target];
|
|
82
|
+
const candidate=mergeDevelopmentRegistration({...base,...registrationContext}),registry=interactionCoverageRegistry(ready.collection,candidate.registry,plan.target)[plan.target],metadata=candidate.metadata.networks[plan.target];
|
|
82
83
|
const requiredAddresses=new Set([...deployed.map(c=>c.address.toLowerCase()),...ready.collection.verification.map(a=>a.address.toLowerCase()),...plan.transactionBindings.filter(b=>b.role==='call').map(b=>plan.transactions.find(t=>t.transactionId===b.transactionId)!.to.toLowerCase())]);
|
|
83
84
|
for(const address of requiredAddresses){
|
|
84
85
|
const rows=Object.entries(registry).filter(([,a])=>a.toLowerCase()===address);if(rows.length!==1)throw Error('Production code target requires unique frozen registration');
|
|
@@ -131,7 +132,7 @@ export async function finalizeProductionExecution(input:{root:string;ready:Produ
|
|
|
131
132
|
function verifyCommitted(journal:any){
|
|
132
133
|
const reg=validateContractInfo(JSON.parse(fileBytes(root,'contractInfo.json')??'{}')),meta=validateDeploymentInfo(JSON.parse(fileBytes(root,'deploymentInfo.json')??'{}'));
|
|
133
134
|
const oldReg=validateContractInfo(JSON.parse(journal.files.find((f:any)=>f.path==='contractInfo.json').after)),oldMeta=validateDeploymentInfo(JSON.parse(journal.files.find((f:any)=>f.path==='deploymentInfo.json').after));
|
|
134
|
-
for(const key of Object.keys(effective[plan.target])){const alias=key.split(':')[0];same(resolveDeployment(reg,plan.target,alias),resolveDeployment(oldReg,plan.target,alias),'Committed production registration');same(meta.networks[plan.target]?.[alias]??null,oldMeta.networks[plan.target]?.[alias]??null,'Committed production metadata');}
|
|
135
|
+
for(const key of Object.keys(effective[plan.target])){const alias=key.split(':')[0];same(resolveDeployment(reg,plan.target,alias)??null,resolveDeployment(oldReg,plan.target,alias)??null,'Committed production registration');same(meta.networks[plan.target]?.[alias]??null,oldMeta.networks[plan.target]?.[alias]??null,'Committed production metadata');}
|
|
135
136
|
for(const f of journal.files){
|
|
136
137
|
if(['contractInfo.json','deploymentInfo.json'].includes(f.path))continue;
|
|
137
138
|
const owned=candidates.filter(c=>c.path===f.path);
|
|
@@ -39,13 +39,14 @@ function inspect(raw: ProductionOptions, command: ProductionCommand) {
|
|
|
39
39
|
const input = deepFreeze(strictData(raw));
|
|
40
40
|
exactObject(input, keys, 'production options');
|
|
41
41
|
safeId(input.executionId, 'executionId');
|
|
42
|
-
const mode = command === 'prepare' ? 'simulate' : command;
|
|
43
|
-
const request = parseDeliveryRequest({ version: 1, environment: 'prod', purpose: 'prod-execution', mode, network: input.target, operator: input.operator, releaseRef: input.releaseRef, sourceRef: { path: input.handoff, taskId: input.taskId, planKey: input.planKey, rev: input.rev } });
|
|
44
42
|
const release = inspectDeliveryRelease(input);
|
|
45
43
|
if (release.status === 'pending')
|
|
46
44
|
return { status: 'pending' as const, input };
|
|
45
|
+
const mode = command === 'prepare' ? 'simulate' : command;
|
|
46
|
+
const native= (require('./release-graph') as typeof import('./release-graph')).standaloneReleaseDefinition(input);
|
|
47
|
+
const request = parseDeliveryRequest({ version: native?2:1, environment: 'prod', purpose: 'prod-execution', mode, network: input.target, operator: input.operator, releaseRef: input.releaseRef, sourceRef: native?{kind:'standalone',path:input.handoff,deploymentId:input.deploymentId}:{ path: input.handoff, taskId: input.taskId, planKey: input.planKey, rev: input.rev } });
|
|
47
48
|
const source = readOwnedDeliverySource({ workspaceRoot: input.workspaceRoot, projectRoot: input.projectRoot, request });
|
|
48
|
-
if ((source.
|
|
49
|
+
if ((source.scope as any).deploymentId !== input.deploymentId)
|
|
49
50
|
throw Error('Production deployment mismatch');
|
|
50
51
|
const target = release.release.targets.find((t: any) => t.id === input.target);
|
|
51
52
|
if (!target)
|
|
@@ -69,7 +70,7 @@ export function inspectDeliveryProduction(raw: ProductionOptions, command: Produ
|
|
|
69
70
|
if(canonicalJson(deliveryCalls)!==canonicalJson(record.deliveryCalls))throw Error('Frozen production public projection mismatch');
|
|
70
71
|
if (record.releaseHash !== current.releaseHash || plan.inputsHash !== current.release.inputsHash || plan.sourceDefinitionHash !== current.release.sourceDefinitionHash || plan.executionId !== current.input.executionId || plan.target !== current.input.target || plan.operator !== current.input.operator.toLowerCase() || plan.environment !== 'prod' || plan.chainId !== String(current.target.chainId))
|
|
71
72
|
throw Error('Production artifact context mismatch');
|
|
72
|
-
return deepFreeze({ status: 'ready' as const, plan, collection: record.collection as DeliveryCollection, deliveryCalls, operations: current.release.operations as any[], finalization: record.finalization, releaseHash: current.releaseHash, artifactHash, sha256: byteHash(bytes), path: artifactPath(current.input.executionId), ...(current.target.safe ? { safeTarget: current.target.safe } : {}) });
|
|
73
|
+
return deepFreeze({ status: 'ready' as const, plan, collection: record.collection as DeliveryCollection, deliveryCalls, parameters:current.release.parameters.find((p:any)=>p.target===current.input.target)?.resolved.values, operations: current.release.operations as any[], finalization: record.finalization, releaseHash: current.releaseHash, artifactHash, sha256: byteHash(bytes), path: artifactPath(current.input.executionId), ...(current.target.safe ? { safeTarget: current.target.safe } : {}) });
|
|
73
74
|
}
|
|
74
75
|
/** Instantiate the delivered business operations, never accept replacement operations or parameters. */
|
|
75
76
|
export async function prepareDeliveryProduction(raw: ProductionOptions) {
|
|
@@ -35,7 +35,10 @@ export function readFile(root: string, relative: string): string | undefined {
|
|
|
35
35
|
return stat(file) ? fs.readFileSync(file, 'utf8') : undefined;
|
|
36
36
|
}
|
|
37
37
|
export function deliveryProjectMarker(root: string): void {
|
|
38
|
-
|
|
38
|
+
const marker = readFile(root, '.gate-template.json');
|
|
39
|
+
// Existing Hardhat projects have no generator provenance. Setup validates their
|
|
40
|
+
// real configuration and local capabilities without inventing a template identity.
|
|
41
|
+
if (marker !== undefined) parseDeliveryProjectMarker(marker);
|
|
39
42
|
}
|
|
40
43
|
/** Validate the same captured marker bytes whose digest is bound to current inputs. */
|
|
41
44
|
export function parseDeliveryProjectMarker(bytes: string | Buffer): void {
|
|
@@ -52,6 +55,7 @@ export function parseDeliveryProjectMarker(bytes: string | Buffer): void {
|
|
|
52
55
|
const optional = ['README.md', 'AGENTS.md', '.gitignore'];
|
|
53
56
|
const assets = ['package.json', 'package-lock.json', 'hardhat.config.ts', 'rpc.env', '.env.example', 'tsconfig.json', ...optional, 'contracts/.gitkeep', 'test/.gitkeep',
|
|
54
57
|
'foundry.toml', 'config.yaml',
|
|
58
|
+
...(Object.hasOwn(files, 'hardhat.zk.config.ts') ? ['hardhat.zk.config.ts'] : []),
|
|
55
59
|
...(Object.hasOwn(files, 'vendor/gate-evm-tools-1.2.0.tgz') ? ['vendor/gate-evm-tools-1.2.0.tgz'] : []),
|
|
56
60
|
...(inputs.examples === true ? ['contracts/Counter.sol', 'test/Counter.ts', 'test/Counter.t.sol'] : [])];
|
|
57
61
|
if (typeof inputs.projectName !== 'string' || !inputs.projectName || !Array.isArray(inputs.networks)
|
|
@@ -10,7 +10,7 @@ export const PROJECT_ENV_BOOTSTRAP = `for (const file of ["rpc.env", ".env"]) {
|
|
|
10
10
|
process.loadEnvFile(envFile);
|
|
11
11
|
} catch (error) {
|
|
12
12
|
if (file !== ".env" || !["ENOENT", "EPERM", "EACCES"].includes((error as NodeJS.ErrnoException).code ?? "")) throw error;
|
|
13
|
-
console.warn("Project credentials are unavailable; compilation and public checks can continue. Use the
|
|
13
|
+
console.warn("Project credentials are unavailable; compilation and public checks can continue. Use the terminal or App delivery host for credential-bearing operations.");
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}`;
|
|
@@ -30,7 +30,11 @@ function statement(source: string): string {
|
|
|
30
30
|
const file = ts.createSourceFile('rpc.ts', source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
31
31
|
return syntax(file.statements[0], file);
|
|
32
32
|
}
|
|
33
|
-
|
|
33
|
+
// Retain the previous public diagnostic spelling used by migrated projects;
|
|
34
|
+
// the loader's file access, precedence and error handling remain exact.
|
|
35
|
+
const bootstrapSyntax = new Set([PROJECT_ENV_BOOTSTRAP,
|
|
36
|
+
PROJECT_ENV_BOOTSTRAP.replace('the terminal or App delivery host for credential-bearing operations', 'the application host for credential-bearing delivery operations'),
|
|
37
|
+
].map(statement)), helperSyntax = statement(PROJECT_RPC_HELPER);
|
|
34
38
|
|
|
35
39
|
export function projectRpcSyntax(file: ts.SourceFile): { statements: Set<ts.Statement>; bootstrap: boolean; helper: boolean } {
|
|
36
40
|
const statements = new Set<ts.Statement>();
|
|
@@ -46,7 +50,7 @@ export function projectRpcSyntax(file: ts.SourceFile): { statements: Set<ts.Stat
|
|
|
46
50
|
|| clause.namedBindings.elements.length !== 1 || clause.namedBindings.elements[0].name.text !== wanted
|
|
47
51
|
|| clause.namedBindings.elements[0].propertyName) throw Error('Unsupported project RPC import');
|
|
48
52
|
imports.add(name); statements.add(node);
|
|
49
|
-
} else if (ts.isForOfStatement(node) && syntax(node, file)
|
|
53
|
+
} else if (ts.isForOfStatement(node) && bootstrapSyntax.has(syntax(node, file))) {
|
|
50
54
|
if (bootstrap) throw Error('Duplicate project RPC loader');
|
|
51
55
|
bootstrap = true; statements.add(node);
|
|
52
56
|
} else if (ts.isFunctionDeclaration(node) && node.name?.text === 'rpcUrl') {
|
|
@@ -97,7 +101,9 @@ function projectEnvironment(root: string, source: string, env: NodeJS.ProcessEnv
|
|
|
97
101
|
for (const [key, value] of Object.entries(parseEnv(contents))) if (effective[key] === undefined) effective[key] = value;
|
|
98
102
|
} catch (error) {
|
|
99
103
|
if (name === '.env' && ['EPERM', 'EACCES'].includes((error as NodeJS.ErrnoException).code ?? '')) {
|
|
100
|
-
|
|
104
|
+
// Match the canonical Hardhat bootstrap: public RPC configuration can proceed
|
|
105
|
+
// without a protected credential file. Signing hosts load credentials separately.
|
|
106
|
+
continue;
|
|
101
107
|
}
|
|
102
108
|
throw Error(`Cannot read local project RPC environment (${name})`);
|
|
103
109
|
}
|
|
@@ -3,12 +3,12 @@ import { validateNetworkAlias } from './policy';
|
|
|
3
3
|
export type DeliveryEnvironment = 'dev' | 'prod';
|
|
4
4
|
export type DeliveryMode = 'simulate' | 'execute' | 'export-calldata' | 'reconcile' | 'verify';
|
|
5
5
|
export interface DeliveryRequest {
|
|
6
|
-
version: 1;
|
|
6
|
+
version: 1 | 2;
|
|
7
7
|
environment: DeliveryEnvironment;
|
|
8
8
|
purpose: 'dev-execution' | 'prod-qualification' | 'prod-execution';
|
|
9
9
|
mode: DeliveryMode;
|
|
10
10
|
network: string;
|
|
11
|
-
sourceRef: { path: string; taskId
|
|
11
|
+
sourceRef: { kind?: 'gateflow' | 'standalone'; path: string; taskId?: string; planKey?: string; rev?: number; deploymentId?: string };
|
|
12
12
|
releaseRef?: { path: string; releaseId: string; inputsHash: string };
|
|
13
13
|
fork?: { blockNumber: number; blockHash: string; readOnly: true };
|
|
14
14
|
devSigner?: string;
|
|
@@ -43,7 +43,7 @@ export function positiveInteger(value: unknown, label: string): number {
|
|
|
43
43
|
}
|
|
44
44
|
export function parseDeliveryRequest(input: unknown): DeliveryRequest {
|
|
45
45
|
const value = object(input, 'delivery request', ['version', 'environment', 'purpose', 'mode', 'network', 'sourceRef', 'releaseRef', 'fork', 'devSigner', 'operator']);
|
|
46
|
-
if (value.version !== 1) throw new Error('Unsupported delivery request version');
|
|
46
|
+
if (value.version !== 1 && value.version !== 2) throw new Error('Unsupported delivery request version');
|
|
47
47
|
if (typeof value.environment !== 'string' || !['dev', 'prod'].includes(value.environment)) throw new Error('environment must be dev or prod');
|
|
48
48
|
if (typeof value.purpose !== 'string' || !['dev-execution', 'prod-qualification', 'prod-execution'].includes(value.purpose)) throw new Error('Invalid purpose');
|
|
49
49
|
const dev = value.purpose === 'dev-execution';
|
|
@@ -51,8 +51,16 @@ export function parseDeliveryRequest(input: unknown): DeliveryRequest {
|
|
|
51
51
|
const modes = dev ? ['simulate', 'execute', 'reconcile', 'verify'] : value.purpose === 'prod-qualification' ? ['simulate', 'verify'] : ['simulate', 'execute', 'export-calldata', 'reconcile', 'verify'];
|
|
52
52
|
if (typeof value.mode !== 'string' || !modes.includes(value.mode)) throw new Error('Explicit valid mode is required for purpose');
|
|
53
53
|
validateNetworkAlias(value.network, 'network', false);
|
|
54
|
-
const source = object(value.sourceRef, 'sourceRef', ['path', 'taskId', 'planKey', 'rev']);
|
|
55
|
-
portablePath(source.path, 'sourceRef.path');
|
|
54
|
+
const source = object(value.sourceRef, 'sourceRef', ['kind', 'path', 'taskId', 'planKey', 'rev', 'deploymentId']);
|
|
55
|
+
portablePath(source.path, 'sourceRef.path');
|
|
56
|
+
if (value.version === 2 && !['standalone','gateflow'].includes(String(source.kind))) throw Error('v2 requires explicit source kind');
|
|
57
|
+
if (source.kind === 'standalone') {
|
|
58
|
+
if (value.version !== 2 || Object.keys(source).some(k=>!['kind','path','deploymentId'].includes(k))) throw Error('Invalid standalone source reference');
|
|
59
|
+
safeId(source.deploymentId, 'source deploymentId');
|
|
60
|
+
} else {
|
|
61
|
+
if (source.kind !== undefined && source.kind !== 'gateflow' || source.deploymentId !== undefined) throw Error('Invalid GateFlow source reference');
|
|
62
|
+
safeId(source.taskId, 'taskId'); safeId(source.planKey, 'planKey'); positiveInteger(source.rev, 'rev');
|
|
63
|
+
}
|
|
56
64
|
if (dev) {
|
|
57
65
|
address(value.devSigner, 'devSigner');
|
|
58
66
|
if ('operator' in value) throw new Error('devSigner and prod operator are separate roles');
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/** Bounded read-only ABI query; no provider or signer is exposed to a business module. */
|
|
2
|
+
import { Interface } from 'ethers';
|
|
3
|
+
import { address, object } from './protocol';
|
|
4
|
+
import { validateAbiArguments, validateAbiValue, normalizeRuntimeValue, compareAbiResults } from './abi';
|
|
5
|
+
import { strictData } from './data';
|
|
6
|
+
import { deepFreeze } from '../workflow/lib/canonicalData';
|
|
7
|
+
import type { ContractQuery } from './scripts';
|
|
8
|
+
export function parseContractQueryEvidence(raw:unknown){
|
|
9
|
+
const e=object(strictData(raw),'contract query evidence',['chainId','block','source','address','abi','method','args','caller','result','returnTypes']);
|
|
10
|
+
if(typeof e.chainId!=='string'||!(/^[1-9][0-9]*$/).test(e.chainId))throw Error('Invalid query chain');
|
|
11
|
+
const block=object(e.block,'query block',['number','hash']),source=object(e.source,'query source',['blockNumber','blockHash']);
|
|
12
|
+
if(typeof block.number!=='string'||!/^0x[\da-f]+$/i.test(block.number)||typeof block.hash!=='string'||!/^0x[\da-f]{64}$/i.test(block.hash)
|
|
13
|
+
||!Number.isSafeInteger(source.blockNumber)||Number(source.blockNumber)<0||typeof source.blockHash!=='string'||!/^0x[\da-f]{64}$/i.test(source.blockHash))throw Error('Invalid query block identity');
|
|
14
|
+
address(e.address,'query');address(e.caller,'query caller');
|
|
15
|
+
if(!Array.isArray(e.abi)||e.abi.length!==1||typeof e.abi[0]!=='string')throw Error('Invalid query ABI');
|
|
16
|
+
const fn=new Interface(e.abi).getFunction(e.method as string);
|
|
17
|
+
if(!fn||!['view','pure'].includes(fn.stateMutability)||fn.format('sighash')!==e.method)throw Error('Invalid read-only query method');
|
|
18
|
+
validateAbiArguments(e.abi,fn.format('sighash'),e.args);
|
|
19
|
+
if(JSON.stringify(fn.outputs.map(p=>p.format('full')))!==JSON.stringify(e.returnTypes))throw Error('Query return types mismatch');
|
|
20
|
+
if(fn.outputs.length===1)validateAbiValue(fn.outputs[0],e.result,'query result');
|
|
21
|
+
else {if(!Array.isArray(e.result)||e.result.length!==fn.outputs.length)throw Error('Invalid query result count');fn.outputs.forEach((p,i)=>validateAbiValue(p,(e.result as unknown[])[i],'query result'));}
|
|
22
|
+
return deepFreeze(e);
|
|
23
|
+
}
|
|
24
|
+
export async function readScriptContract(query: ContractQuery, input:{request(method:string,params:unknown[]):Promise<any>;chainId:string;block:{number:string;hash:string};caller:string;source:{blockNumber:number;blockHash:string}}){
|
|
25
|
+
const q=object(strictData(query),'contract query',['address','abi','method','args','caller']);
|
|
26
|
+
address(q.address,'query');if(!Array.isArray(q.abi)||q.abi.some(x=>typeof x!=='string'))throw Error('Query ABI must use function strings');
|
|
27
|
+
const iface=new Interface(q.abi),fn=iface.getFunction(q.method as string);if(!fn||!['view','pure'].includes(fn.stateMutability))throw Error('readContract requires a view/pure function ABI');
|
|
28
|
+
const caller=address(q.caller??input.caller,'query caller'),args=q.args??[];validateAbiArguments(q.abi,fn.format('sighash'),args);
|
|
29
|
+
const data=iface.encodeFunctionData(fn,args as any[]),raw=await input.request('eth_call',[{to:q.address,from:caller,data},input.block.number]);
|
|
30
|
+
const decoded=iface.decodeFunctionResult(fn,raw),value=normalizeRuntimeValue(fn.outputs.length===1?decoded[0]:decoded);
|
|
31
|
+
const block=await input.request('eth_getBlockByNumber',[input.block.number,false]);if(block?.hash!==input.block.hash)throw Error('Query block changed');
|
|
32
|
+
return deepFreeze({value,evidence:parseContractQueryEvidence({chainId:input.chainId,block:input.block,source:input.source,address:(q.address as string).toLowerCase(),abi:[fn.format('full')],method:fn.format('sighash'),args,caller:caller.toLowerCase(),result:value,returnTypes:fn.outputs.map(p=>p.format('full'))})});
|
|
33
|
+
}
|
|
34
|
+
export function requireQueryResult(query:ContractQuery & {expect:unknown},actual:unknown){const c=compareAbiResults(query.abi,query.method,actual,query.expect);if(c.actual!==c.expected)throw Error(`Unmet live dependency ${query.method}: expected ${c.expected}, got ${c.actual}`);}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {readStandaloneTask} from './standalone-source';
|
|
1
2
|
/** Actual-file release graph classification. No caller-selected roots or target subsets. */
|
|
2
3
|
import {parseCanonicalPlan,projectCanonicalDefinitions,deploymentPurpose} from './source';
|
|
3
4
|
import {projectRoot} from './project-files';
|
|
@@ -31,7 +32,23 @@ export function readReleaseUniverse(input:Omit<ReleaseGraphOptions,'releaseRef'>
|
|
|
31
32
|
const upstream=projectCanonicalDefinitions(text,{roots:qualifications.map(t=>t.planKey),mode:'upstream'});
|
|
32
33
|
return {workspace,root,text,plan,entries,relevant,targets,qualifications,upstream,developmentNetwork:task.developmentNetwork as string};
|
|
33
34
|
}
|
|
35
|
+
export function standaloneReleaseDefinition(input:Omit<ReleaseGraphOptions,'releaseRef'>){
|
|
36
|
+
const root=projectRoot(input.workspaceRoot,input.projectRoot),relative=`scripts/tasks/${safeId(input.deploymentId,'deploymentId')}/task.yaml`;
|
|
37
|
+
if(input.handoff!==relative)return undefined;
|
|
38
|
+
const task=object(parseInputDocument(readInputBytes(root,relative),'yaml'),'task');
|
|
39
|
+
if(task.source!=='standalone')return undefined;
|
|
40
|
+
if(input.handoff!==relative||input.taskId!==input.deploymentId)throw Error('Native release source mismatch');
|
|
41
|
+
return readStandaloneTask(root,relative);
|
|
42
|
+
}
|
|
43
|
+
export function selectedReleaseTargets(input:Omit<ReleaseGraphOptions,'releaseRef'>){return standaloneReleaseDefinition(input)?.releaseTargetNetworks??readReleaseUniverse(input).targets;}
|
|
34
44
|
export function readReleaseGraph(input:ReleaseGraphOptions){
|
|
45
|
+
const native=standaloneReleaseDefinition(input);
|
|
46
|
+
if(native){
|
|
47
|
+
safeId(input.releaseRef.releaseId,'releaseId');if(input.releaseRef.path!==`scripts/tasks/${input.deploymentId}/releases/${input.releaseRef.releaseId}/release.json`||!/^[a-f0-9]{64}$/.test(input.releaseRef.inputsHash))throw Error('Invalid native release reference');
|
|
48
|
+
const graph={schema:'gate-delivery-standalone-release',version:1,definition:native,releaseRef:input.releaseRef,targets:native.releaseTargetNetworks,stages:['qualify','prepare','execute-or-export','reconcile','verify']};
|
|
49
|
+
return deepFreeze({targets:native.releaseTargetNetworks,graph,prodTaskGraphHash:semanticHash(graph)});
|
|
50
|
+
}
|
|
51
|
+
|
|
35
52
|
const u=readReleaseUniverse(input);safeId(input.releaseRef.releaseId,'releaseId');portablePath(input.releaseRef.path,'releaseRef.path');if(!/^[a-f0-9]{64}$/.test(input.releaseRef.inputsHash))throw Error('Invalid release input hash');
|
|
36
53
|
// Classify ALL relevant production tasks first. Filtering a good ref first conceals split/wrong bindings.
|
|
37
54
|
const groups=new Map<string,typeof u.relevant>();
|
|
@@ -1,32 +1,43 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Preserve accepted development inputs and one shared, compressed producer snapshot. */
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {gzipSync} from 'node:zlib';
|
|
5
5
|
import {readInputBytes} from './input-files';
|
|
6
6
|
import {confinedFile,projectRoot} from './project-files';
|
|
7
|
-
import {
|
|
7
|
+
import {byteHash,developmentPath} from './materialization';
|
|
8
8
|
import {canonicalJson,semanticHash} from '../workflow/lib/canonicalData';
|
|
9
9
|
import {parseDeliveryPlan} from './plan';
|
|
10
10
|
import {safeId} from './protocol';
|
|
11
|
+
import {persistDevelopmentInputs,writeImmutableDeliveryFile} from './development-inputs';
|
|
11
12
|
import type {DeliveryAggregate,DeliveryAggregateOptions} from './aggregate';
|
|
12
13
|
import type {HistoricalDevelopmentFiles} from './release-history';
|
|
13
14
|
export function captureDevelopmentReleaseHistory(input:{root:string;source:DeliveryAggregateOptions;aggregate:DeliveryAggregate}):HistoricalDevelopmentFiles {
|
|
14
15
|
const root=fs.realpathSync(input.root),a=input.aggregate,plan=parseDeliveryPlan(a.collections[0]?.plan);
|
|
15
16
|
if(input.source.selection!=='development'||a.collections.length!==1||plan.environment!=='dev'||projectRoot(input.source.workspaceRoot,input.source.projectRoot)!==root)throw Error('Historical capture requires original development source');
|
|
16
17
|
safeId(plan.executionId,'executionId');
|
|
17
|
-
const base=`.gate-delivery/history/${plan.executionId}`,
|
|
18
|
+
const base=`.gate-delivery/history/${plan.executionId}`,readyFile=`${base}/READY.json`;
|
|
18
19
|
const acceptanceFile=`.gate-delivery/development/${plan.executionId}.acceptance.json`,acceptance=JSON.parse(readInputBytes(root,acceptanceFile).toString());
|
|
19
20
|
if(acceptance.status!=='accepted'||acceptance.planHash!==plan.planHash||acceptance.inputsHash!==a.inputsHash||semanticHash(acceptance.runtimeIdentity)!==semanticHash(plan.runtimeIdentity))throw Error('Historical capture requires actual accepted development');
|
|
20
21
|
const original=a.inputs.runtime.package;if(original.name!=='gate-evm-tools'||original.lockLocation!=='node_modules/gate-evm-tools')throw Error('Unsupported historical producer location');
|
|
22
|
+
// A completed capture can be reused after the installed producer changes.
|
|
23
|
+
if(fs.existsSync(developmentPath(root,readyFile))){
|
|
24
|
+
const ready=JSON.parse(readInputBytes(root,readyFile).toString());
|
|
25
|
+
if(ready.schema!=='gate-delivery-history-ready'||ready.version!==2||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(a)||ready.sourceHash!==semanticHash(input.source)||ready.descriptor?.version!==2||ready.descriptor.readyFile!==readyFile||!Array.isArray(ready.files)||!ready.files.length)throw Error('Conflicting historical capture');
|
|
26
|
+
for(const f of ready.files)if(byteHash(readInputBytes(root,f.path))!==f.sha256)throw Error('Captured historical file drift');
|
|
27
|
+
return ready.descriptor;
|
|
28
|
+
}
|
|
21
29
|
const files:Array<{path:string;digest:string;base64:string}>=[];
|
|
22
30
|
const visit=(relative:string)=>{const dir=confinedFile(root,`${original.lockLocation}/${relative}`.replace(/\/$/,''),'directory');for(const name of fs.readdirSync(dir).sort()){const rel=relative?`${relative}/${name}`:name;const stat=fs.lstatSync(path.join(dir,name));if(stat.isDirectory()){if(name==='node_modules')continue;visit(rel);}else{const bytes=readInputBytes(root,`${original.lockLocation}/${rel}`);files.push({path:rel,digest:byteHash(bytes),base64:bytes.toString('base64')});}}};visit('');files.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);
|
|
23
31
|
if(semanticHash(files.map(({path,digest})=>({path,digest})))!==semanticHash(original.payload))throw Error('Historical capture must precede producer/runtime replacement');
|
|
24
|
-
const
|
|
25
|
-
|
|
32
|
+
const archive=gzipSync(Buffer.from(canonicalJson({schema:'gate-delivery-package-snapshot',version:2,runtimeIdentity:plan.runtimeIdentity,observedPackage:original,files})));
|
|
33
|
+
const archiveFile=`.gate-delivery/packages/${byteHash(archive)}.json.gz`;
|
|
26
34
|
const saved:Array<{path:string;sha256:string}>=[];
|
|
27
|
-
const write=(relative:string,bytes:Buffer)=>
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
const write=(relative:string,bytes:Buffer)=>saved.push(writeImmutableDeliveryFile(root,relative,bytes));
|
|
36
|
+
const {sourcePath:sourceFile,aggregatePath:aggregateFile}=persistDevelopmentInputs(input);
|
|
37
|
+
const descriptor:HistoricalDevelopmentFiles={version:2,aggregateFile,sourceFile,acceptanceFile,readyFile,inputsRoot:`${base}/inputs`,archiveFile};
|
|
38
|
+
for(const relative of [sourceFile,aggregateFile,acceptanceFile])saved.push({path:relative,sha256:byteHash(readInputBytes(root,relative))});
|
|
39
|
+
const snapshot=JSON.parse(readInputBytes(root,`.gate-delivery/development/${plan.executionId}.inputs.snapshot.json`).toString());
|
|
40
|
+
for(const name of [`${a.inputs.business.businessRoot}/task.yaml`,`${a.inputs.business.businessRoot}/parameters.yaml`,`${a.inputs.business.businessRoot}/parameters.provenance.json`,'config.yaml','hardhat.config.ts','package-lock.json','contractInfo.json','deploymentInfo.json']){const original=snapshot.files.find((f:any)=>f.path===name);write(`${base}/inputs/${name}`,original?Buffer.from(original.base64,'base64'):readInputBytes(root,name));}
|
|
41
|
+
write(archiveFile,archive);
|
|
42
|
+
writeImmutableDeliveryFile(root,readyFile,Buffer.from(canonicalJson({schema:'gate-delivery-history-ready',version:2,generation:'complete',planHash:plan.planHash,aggregateHash:semanticHash(a),sourceHash:semanticHash(input.source),descriptor,files:saved})+'\n'));return descriptor;
|
|
32
43
|
}
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
/** Read-only consumption of R1 evidence. Historical runtime is verified, never executed or relabelled. */
|
|
2
|
-
import {existingCodeCoverage,requiredExistingAddresses} from './existing-code';
|
|
2
|
+
import {existingCodeCoverage,requiredExistingAddresses,interactionCoverageRegistry} from './existing-code';
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import {aggregateInputHash} from './execution-identity';
|
|
5
5
|
export {aggregateInputHash} from './execution-identity';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import {createHash} from 'node:crypto';
|
|
8
|
-
import {gzipSync,gunzipSync} from 'node:zlib';
|
|
9
7
|
import {Contract,JsonRpcProvider} from 'ethers';
|
|
10
8
|
import {BytecodeChecker,TRANSPARENT_PROXY_ARTIFACT,PROXY_ADMIN_ARTIFACT,ERC1967_ADMIN_SLOT,PROXY_ADMIN_INTERFACE,transparentAdminAddress,assertTransparentAdminImmutable} from 'gate-evm-tools/workflow';
|
|
11
9
|
import {readInputBytes,inputDigest,parseInputDocument} from './input-files';
|
|
12
|
-
import {
|
|
10
|
+
import {projectRoot} from './project-files';
|
|
13
11
|
import {parseCanonicalPlan,projectCanonicalDefinitions} from './source';
|
|
14
12
|
import {parseConfigurationDefinition} from './configuration-definition';
|
|
15
13
|
import {parseDeliveryPublicTargets} from './config';
|
|
16
|
-
import {object} from './protocol';
|
|
14
|
+
import {object,safeId} from './protocol';
|
|
15
|
+
import {readHistoricalPackage} from './history-package';
|
|
17
16
|
import {readProjectNetworkSnapshots} from './project';
|
|
18
17
|
import {parseDeliveryPlan} from './plan';
|
|
19
18
|
import {parsePreparationJournal} from './journal';
|
|
@@ -33,35 +32,32 @@ import {resolveParameterTargets} from './parameter-defaults';
|
|
|
33
32
|
import {validateHistoricalParameterExtension,type ParameterInputs} from './parameters';
|
|
34
33
|
import {validateHistoricalBusinessExtension} from './business-extension';
|
|
35
34
|
import {parseNativeOperation} from './custom';
|
|
36
|
-
export interface HistoricalDevelopmentFiles {aggregateFile:string;sourceFile:string;acceptanceFile:string;
|
|
35
|
+
export interface HistoricalDevelopmentFiles {version:2;aggregateFile:string;sourceFile:string;acceptanceFile:string;readyFile:string;inputsRoot:string;archiveFile:string}
|
|
37
36
|
function decode(raw:any):any{const v=parseTypedValue(raw);switch(v.type){case 'bigint':return BigInt(v.value);case 'number':return Number(v.value);case 'array':return v.value.map(decode);case 'object':return Object.fromEntries(Object.entries(v.value).map(([k,x])=>[k,decode(x)]));default:return v.value;}}
|
|
38
37
|
/** Every mutable historical container is retained with its ORIGINAL digest; allowed deltas are explicit. */
|
|
39
38
|
export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceRoot:string;projectRoot:string;history:HistoricalDevelopmentFiles}){
|
|
40
39
|
const workspace=projectRoot(input.workspaceRoot,'.'),root=projectRoot(workspace,input.projectRoot),{history}=input,files=new Map<string,string>();
|
|
41
40
|
if(fs.realpathSync(input.root)!==root)throw Error('Historical current project root mismatch');
|
|
41
|
+
object(history,'historical descriptor',['version','aggregateFile','sourceFile','acceptanceFile','readyFile','inputsRoot','archiveFile']);
|
|
42
|
+
if(history.version!==2)throw Error('Unsupported historical descriptor version');
|
|
42
43
|
const read=(relative:string)=>{const bytes=readInputBytes(root,relative);files.set(relative,inputDigest(bytes));return bytes;};
|
|
43
44
|
const aggregate=JSON.parse(read(history.aggregateFile).toString()) as DeliveryAggregate,source=JSON.parse(read(history.sourceFile).toString()) as DeliveryAggregateOptions,acceptance=JSON.parse(read(history.acceptanceFile).toString());
|
|
44
45
|
if(source.projectRoot!==input.projectRoot)throw Error('Historical relative project scope mismatch');
|
|
45
46
|
if(source.selection!=='development'||source.targets.length!==1||aggregate.collections.length!==1||aggregate.inputs.business.targets.length!==1)throw Error('Historical development scope mismatch');
|
|
46
47
|
same(aggregateInputHash(aggregate),aggregate.inputsHash,'Historical aggregate inputs');same(semanticHash(aggregate.inputs.sourceDefinitions),aggregate.sourceDefinitionHash,'Historical source hash');
|
|
47
48
|
const collection=aggregate.collections[0],plan=parseDeliveryPlan(collection.plan),target=aggregate.inputs.business.targets[0];
|
|
49
|
+
const id=safeId(plan.executionId,'executionId'),base=`.gate-delivery/history/${id}`;
|
|
50
|
+
if(history.aggregateFile!==`.gate-delivery/development/${id}.aggregate.json`||history.sourceFile!==`.gate-delivery/development/${id}.source.json`||history.acceptanceFile!==`.gate-delivery/development/${id}.acceptance.json`||history.readyFile!==`${base}/READY.json`||history.inputsRoot!==`${base}/inputs`||!/^\.gate-delivery\/packages\/[a-f0-9]{64}\.json\.gz$/.test(history.archiveFile))throw Error('Noncanonical historical descriptor');
|
|
48
51
|
if(plan.environment!=='dev'||plan.target!==target.id||source.targets[0].request.network!==plan.target||source.targets[0].executionId!==plan.executionId)throw Error('Historical development identity mismatch');
|
|
49
52
|
for(const key of ['executionId','planHash','target','inputsHash','sourceDefinitionHash','runtimeIdentity'] as const)same(acceptance[key],plan[key],`Historical acceptance ${key}`);
|
|
50
53
|
if(acceptance.version!==1||acceptance.status!=='accepted')throw Error('Missing historical acceptance');
|
|
51
|
-
const
|
|
52
|
-
if(ready.schema!=='gate-delivery-history-ready'||ready.version!==
|
|
54
|
+
const ready=JSON.parse(read(history.readyFile).toString());
|
|
55
|
+
if(ready.schema!=='gate-delivery-history-ready'||ready.version!==2||ready.generation!=='complete'||ready.planHash!==plan.planHash||ready.aggregateHash!==semanticHash(aggregate)||ready.sourceHash!==semanticHash(source)||!Array.isArray(ready.files)||!ready.files.length)throw Error('Historical snapshot generation is not READY');
|
|
53
56
|
same(ready.descriptor,history,'Historical READY descriptor');for(const file of ready.files)if(byteHash(read(file.path))!==file.sha256)throw Error('Historical READY file binding mismatch');
|
|
54
57
|
const original=aggregate.inputs.runtime.package;
|
|
55
58
|
same(aggregate.inputs.runtime.declared,plan.runtimeIdentity,'Historical runtime identity');
|
|
56
59
|
if(original.name!=='gate-evm-tools'||original.version!==plan.runtimeIdentity.version||original.source.identity!==original.integrity)throw Error('Historical producer provenance mismatch');
|
|
57
|
-
const archive=read(history.archiveFile),
|
|
58
|
-
if(snapshot.schema!=='gate-delivery-package-snapshot'||snapshot.version!==1||!Array.isArray(snapshot.files))throw Error('Invalid historical package snapshot archive');
|
|
59
|
-
const archived=snapshot.files.map((f:any)=>{const bytes=Buffer.from(f.base64,'base64');if(bytes.toString('base64')!==f.base64||byteHash(bytes)!==f.digest)throw Error('Historical snapshot archive bytes mismatch');return {path:f.path,digest:f.digest};});
|
|
60
|
-
same(archived,original.payload,'Historical archive complete payload');same(snapshot.observedPackage,original,'Historical snapshot provenance');same(snapshot.runtimeIdentity,plan.runtimeIdentity,'Historical snapshot runtime');
|
|
61
|
-
const payload:Array<{path:string;digest:string}>=[];
|
|
62
|
-
const visit=(relative:string)=>{const dir=confinedFile(root,`${history.packageRoot}/${relative}`.replace(/\/$/,''),'directory');for(const name of fs.readdirSync(dir).sort()){if(name==='node_modules')throw Error('Historical producer nested package unsupported');const rel=relative?`${relative}/${name}`:name,p=confinedFile(root,`${history.packageRoot}/${rel}`,fs.lstatSync(path.join(dir,name)).isDirectory()?'directory':'file');if(fs.lstatSync(p).isDirectory())visit(rel);else payload.push({path:rel,digest:inputDigest(read(`${history.packageRoot}/${rel}`))});}};
|
|
63
|
-
visit('');payload.sort((a,b)=>a.path<b.path?-1:a.path>b.path?1:0);same(payload,original.payload,'Historical producer actual payload');same(byteHash(payload.map(f=>`${f.path}\0${f.digest}\n`).join('')),original.payloadDigest,'Historical payload digest');
|
|
64
|
-
const digest=createHash('sha256');const hashDir=(dir:string)=>{for(const name of fs.readdirSync(confinedFile(root,`${history.packageRoot}/${dir}`,'directory')).sort()){const rel=`${dir}/${name}`,file=confinedFile(root,`${history.packageRoot}/${rel}`,fs.lstatSync(path.join(root,history.packageRoot,rel)).isDirectory()?'directory':'file');if(fs.lstatSync(file).isDirectory())hashDir(rel);else if(/\.(?:c?js)$/.test(rel)){digest.update(rel);digest.update(read(`${history.packageRoot}/${rel}`));}}};for(const dir of ['dist-runtime','dist','dist-cjs','bin'])hashDir(dir);same(digest.digest('hex'),plan.runtimeIdentity.digest,'Historical executable runtime digest');
|
|
60
|
+
const archive=read(history.archiveFile),packageFiles=readHistoricalPackage(archive,history.archiveFile,aggregate);
|
|
65
61
|
const business=aggregate.inputs.business.businessRoot,mutable=new Set([`${business}/task.yaml`,`${business}/parameters.yaml`,`${business}/parameters.provenance.json`,'config.yaml','hardhat.config.ts']);
|
|
66
62
|
const old=(file:string)=>read(`${history.inputsRoot}/${file}`),now=(file:string)=>read(file);
|
|
67
63
|
const evolving=new Set([`${business}/manifest.yaml`,`${business}/parameters.schema.json`].filter(file=>{
|
|
@@ -81,7 +77,7 @@ export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceR
|
|
|
81
77
|
}
|
|
82
78
|
const ownPayload=new Set(original.payload.map(file=>`${original.lockLocation}/${file.path}`));
|
|
83
79
|
// The entire old frozen file union is verified. Only owned producer bytes and bounded containers have historical locations.
|
|
84
|
-
for(const file of aggregate.inputs.files){if(evolving.has(file.path))continue;const bytes=ownPayload.has(file.path)?
|
|
80
|
+
for(const file of aggregate.inputs.files){if(evolving.has(file.path))continue;const bytes=ownPayload.has(file.path)?packageFiles.get(file.path.slice(original.lockLocation.length+1))!:mutable.has(file.path)?old(file.path):now(file.path);if(inputDigest(bytes)!==file.digest)throw Error(`Historical immutable input drift: ${file.path}`);}
|
|
85
81
|
const oldTask=parseInputDocument(old(`${business}/task.yaml`),'yaml') as any,newTask=parseInputDocument(now(`${business}/task.yaml`),'yaml') as any;
|
|
86
82
|
const {productionRequirementRef:_production,...newBase}=newTask;
|
|
87
83
|
same({...newBase,releaseTargetNetworks:oldTask.releaseTargetNetworks,...(oldTask.productionRequirementRef===undefined?{}:{productionRequirementRef:oldTask.productionRequirementRef})},oldTask,'Historical development task container');
|
|
@@ -114,10 +110,15 @@ export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceR
|
|
|
114
110
|
// A custom script reading a whole changed container makes the relaxed projection unsafe.
|
|
115
111
|
const dependencyFiles=[...aggregate.inputs.targets.flatMap(t=>t.custom?.files??[]),...aggregate.inputs.business.parameters.flatMap(p=>p.closure?.files??[])];
|
|
116
112
|
for(const file of dependencyFiles)if(inputDigest(now(file.path))!==file.digest)throw Error('Historical custom/producer dependency changed');
|
|
113
|
+
if(source.targets[0].request.sourceRef.kind==='standalone'){
|
|
114
|
+
const native=require('./standalone-source').readStandaloneTask(root,source.targets[0].request.sourceRef.path);
|
|
115
|
+
const historical=(aggregate.inputs.sourceDefinitions as any[]);same({...native,releaseTargetNetworks:oldTask.releaseTargetNetworks},historical[0]?.definition,'Historical native scope with bounded prod expansion');
|
|
116
|
+
} else {
|
|
117
117
|
const handoff=source.targets[0].request.sourceRef.path,text=readInputBytes(workspace,handoff).toString(),actual=parseCanonicalPlan(text);if(actual.taskId!==source.targets[0].request.sourceRef.taskId)throw Error('Historical task source identity mismatch');
|
|
118
|
-
const projection=projectCanonicalDefinitions(text,{roots:[source.targets[0].request.sourceRef.planKey],mode:'upstream'}).snapshot;
|
|
118
|
+
const projection=projectCanonicalDefinitions(text,{roots:[source.targets[0].request.sourceRef.planKey!],mode:'upstream'}).snapshot;
|
|
119
119
|
const normalize=(v:any)=>{const copy=JSON.parse(canonicalJson(v));for(const t of copy.tasks)if(t.scope?.deployment&&Object.hasOwn(t.scope.deployment,'releaseTargetNetworks'))t.scope.deployment.releaseTargetNetworks=oldTask.releaseTargetNetworks;return copy;};
|
|
120
120
|
const historical=(aggregate.inputs.sourceDefinitions as any[]);if(historical.length!==1||historical[0].path!==handoff)throw Error('Historical source document mismatch');same(normalize(projection),historical[0].definition,'Historical upstream definition with bounded prod expansion');
|
|
121
|
+
}
|
|
121
122
|
const expected=expectedDevelopmentAssertions(collection,aggregate.inputs.business.steps);
|
|
122
123
|
if(!Array.isArray(acceptance.assertions)||acceptance.assertions.length!==expected.length)throw Error('Missing development business assertions');
|
|
123
124
|
for(const [index,raw] of acceptance.assertions.entries()){const assertion=parseVerificationObservation(raw);same({...assertion,actual:expected[index].actual},expected[index],'Historical complete typed assertion');const comparable=compareAbiResults(assertion.abi,assertion.method,decode(assertion.actual),decode(assertion.expected));if(comparable.actual!==comparable.expected)throw Error('Historical business assertion failed');}
|
|
@@ -140,7 +141,7 @@ export function validateHistoricalDevelopmentFiles(input:{root:string;workspaceR
|
|
|
140
141
|
if(!Array.isArray(record.changes))throw Error('Missing development registry changes');
|
|
141
142
|
const expectedCode:ExpectedDevelopmentCode[]=[],effective:any={[plan.target]:{}};
|
|
142
143
|
for(const change of record.changes){effective[plan.target][change.contractInfoKey]=change.address;const key=change.contractInfoKey.split(':')[0];expectedCode.push({key,contract:change.actualContract,address:change.implementationAddress??change.address});if(change.proxyKind)expectedCode.push({key:`${key}_Proxy`,contract:change.proxyKind==='transparent'?'TransparentUpgradeableProxy':'ERC1967Proxy',address:change.address});if(change.proxyKind==='transparent')expectedCode.push({key:`${key}_ProxyAdmin`,contract:'ProxyAdmin',address:change.proxyAdminAddress});}
|
|
143
|
-
const pure=!preparations.length?existingCodeCoverage({target:plan.target,addresses:requiredExistingAddresses(collection),registry:JSON.parse(historicalRegistration('contractInfo.json').toString()),metadata:JSON.parse(historicalRegistration('deploymentInfo.json').toString()),compiler:aggregate.inputs.targets.find(t=>t.target===plan.target)!.compiler,manifests:(acceptance.materialization.manifests??[]).map((m:any)=>({path:m.file,contents:now(m.file).toString()}))}):null;
|
|
144
|
+
const pure=!preparations.length?existingCodeCoverage({target:plan.target,addresses:requiredExistingAddresses(collection),registry:interactionCoverageRegistry(collection,JSON.parse(historicalRegistration('contractInfo.json').toString()),plan.target),metadata:JSON.parse(historicalRegistration('deploymentInfo.json').toString()),compiler:aggregate.inputs.targets.find(t=>t.target===plan.target)!.compiler,manifests:(acceptance.materialization.manifests??[]).map((m:any)=>({path:m.file,contents:now(m.file).toString()}))}):null;
|
|
144
145
|
if(pure){if(record.changes.length)throw Error('Pure-call history cannot invent deployment changes');Object.assign(effective,pure.effective);expectedCode.push(...pure.expected);same(acceptance.materialization.manifests,pure.manifests,'Historical existing manifests');}
|
|
145
146
|
const code=acceptance.checkCode;if(code?.version!==1||code.executionId!==plan.executionId||code.planHash!==plan.planHash||code.target!==plan.target||typeof code.rawFile!=='string'||!code.rawFile.startsWith(`.gate-delivery/check-code/${plan.executionId}/`))throw Error('Missing real CheckCode identity');
|
|
146
147
|
same(byteHash(historicalRegistration('contractInfo.json')),code.registryHash,'Historical registry');same(byteHash(historicalRegistration('deploymentInfo.json')),code.metadataHash,'Historical deployment metadata');same(byteHash(canonicalJson(effective)+'\n'),code.effectiveHash,'Historical CheckCode effective registry');
|
|
@@ -13,7 +13,7 @@ import {parsePreparationJournal} from './journal';
|
|
|
13
13
|
import {expectedDevelopmentAssertions} from './development';
|
|
14
14
|
import {verifyCapturedForkAudit} from './forkAudit';
|
|
15
15
|
import {parseCanonicalPlan,projectCanonicalDefinitions} from './source';
|
|
16
|
-
import {readReleaseUniverse,currentReleasePrerequisites,readReleaseGraph,isDevelopmentAcceptancePrerequisite,sameReleaseData as same,type ReleaseGraphOptions} from './release-graph';
|
|
16
|
+
import {readReleaseUniverse,standaloneReleaseDefinition,selectedReleaseTargets,currentReleasePrerequisites,readReleaseGraph,isDevelopmentAcceptancePrerequisite,sameReleaseData as same,type ReleaseGraphOptions} from './release-graph';
|
|
17
17
|
import {aggregateInputHash,validateHistoricalDevelopmentFiles,verifyHistoricalDevelopmentChain,type HistoricalDevelopmentFiles} from './release-history';
|
|
18
18
|
import {atomicDevelopmentWrite,fileBytes,withDevelopmentFileLock,byteHash} from './materialization';
|
|
19
19
|
import {canonicalJson,deepFreeze,semanticHash,exactObject} from '../workflow/lib/canonicalData';
|
|
@@ -49,6 +49,8 @@ function taskEvidence(input:Scope&{planKey:string}){
|
|
|
49
49
|
export function recordDeliveryPrerequisite(input:Scope&{planKey:string}){const {root,envelope}=taskEvidence(input);atomicDevelopmentWrite(root,prerequisitePath(input.taskId,input.planKey),encode(envelope));return deepFreeze(envelope);}
|
|
50
50
|
export function validateDeliveryPrerequisite(input:Scope&{planKey:string}){const {root,envelope}=taskEvidence(input),actual=JSON.parse(readInputBytes(root,prerequisitePath(input.taskId,input.planKey)).toString());same(actual,envelope,'Current prerequisite report/artifact binding');return deepFreeze(actual);}
|
|
51
51
|
function prerequisites(scope:Scope,history:ReturnType<typeof validateHistoricalDevelopmentFiles>){
|
|
52
|
+
const native=standaloneReleaseDefinition(scope);
|
|
53
|
+
if(native){if(history.source.targets[0].request.sourceRef.kind!=='standalone'||history.plan.deploymentId!==native.deploymentId||history.plan.target!==native.developmentNetwork)throw Error('Native release requires its own real accepted development');return [{source:'standalone',executionId:history.plan.executionId,planHash:history.plan.planHash,acceptanceHash:semanticHash(history.acceptance)}];}
|
|
52
54
|
const current=currentReleasePrerequisites(scope),receipts=[];
|
|
53
55
|
for(const {task} of current.required){if(isDevelopmentAcceptancePrerequisite(task)){const d=task.scope?.deployment as any;if(d?.environment!=='dev'||d.targetNetwork!==history.plan.target||d.deploymentId!==history.plan.deploymentId)throw Error('Development prerequisite scope mismatch');if(!verificationOnly(task)&&(task.planKey!==history.source.targets[0].request.sourceRef.planKey||task.rev!==history.source.targets[0].request.sourceRef.rev))throw Error('Stale development prerequisite revision');const evidence=task.result?.evidence;if(!Array.isArray(evidence)||!evidence.some((e:any)=>e.ref===scopePath(history,verificationOnly(task))))throw Error('Development prerequisite must reference actual acceptance/CheckCode');receipts.push({planKey:task.planKey,rev:task.rev,definitionHash:semanticHash(projectCanonicalDefinitions(current.text,{roots:[task.planKey],mode:'full'}).snapshot),evidenceHash:semanticHash(verificationOnly(task)?history.acceptance.checkCode:history.acceptance)});}
|
|
54
56
|
else receipts.push(validateDeliveryPrerequisite({...scope,planKey:task.planKey}));}
|
|
@@ -83,16 +85,17 @@ function currentCandidate(scope:Scope,c:any,requireCommittedSources=false){
|
|
|
83
85
|
same(openAggregateSources({workspaceRoot:scope.workspaceRoot,projectRoot:scope.projectRoot,requests:c.targets.map((t:any)=>t.request)}).definitions,c.aggregate.inputs.sourceDefinitions,'Current full upstream source');
|
|
84
86
|
const h=validateHistoricalDevelopmentFiles({root,workspaceRoot:scope.workspaceRoot,projectRoot:scope.projectRoot,history:c.development});same(h.association,c.historical.association,'Historical development association');same(prerequisites(scope,h),c.prerequisites,'Current prerequisite evidence');
|
|
85
87
|
same(h.files.filter(f=>historicalRegistrationFile(f.path)),c.historical.files.filter((f:any)=>historicalRegistrationFile(f.path)),'Original historical registry baseline');
|
|
86
|
-
same(
|
|
88
|
+
same(selectedReleaseTargets(scope),c.aggregate.inputs.business.targets.map((t:any)=>t.id),'Current full selected target set');
|
|
87
89
|
return {root,source,history:h};
|
|
88
90
|
}
|
|
89
91
|
/** Qualification is generated by the actual owned aggregate, never accepted from caller JSON. */
|
|
90
92
|
export async function prepareDeliveryRelease(raw:ReleasePreparationOptions){
|
|
91
93
|
const input=strictData(raw);exactObject(input,['workspaceRoot','projectRoot','handoff','taskId','deploymentId','releaseId','targets','development'],'release preparation');safeId(input.releaseId,'releaseId');
|
|
92
|
-
const {workspaceRoot,projectRoot:relative,handoff,taskId,deploymentId}=input,scope={workspaceRoot,projectRoot:relative,handoff,taskId,deploymentId},u=readReleaseUniverse(scope),root=
|
|
94
|
+
const {workspaceRoot,projectRoot:relative,handoff,taskId,deploymentId}=input,scope={workspaceRoot,projectRoot:relative,handoff,taskId,deploymentId},native=standaloneReleaseDefinition(scope),u=native?undefined:readReleaseUniverse(scope),root=projectRoot(workspaceRoot,relative);
|
|
93
95
|
if(path.resolve(process.cwd())!==root)throw Error('Release preparation must run from actual project root');
|
|
94
96
|
if(fileBytes(root,`scripts/tasks/${deploymentId}/releases/${input.releaseId}/release.json`)!==null)throw Error('Release already has a ready marker');
|
|
95
|
-
same(input.targets.map(t=>({network:t.request.network,
|
|
97
|
+
if(native){same(input.targets.map(t=>({network:t.request.network,kind:t.request.sourceRef.kind,path:t.request.sourceRef.path,deploymentId:t.request.sourceRef.deploymentId,purpose:t.request.purpose})),native.releaseTargetNetworks.map(network=>({network,kind:'standalone',path:handoff,deploymentId,purpose:'prod-qualification'})),'Native exact qualification targets');}
|
|
98
|
+
else same(input.targets.map(t=>({network:t.request.network,sourceRef:t.request.sourceRef,purpose:t.request.purpose,environment:t.request.environment,mode:t.request.mode})),u!.qualifications.map(t=>({network:(t.scope!.deployment as any).targetNetwork,sourceRef:{path:handoff,taskId,planKey:t.planKey,rev:t.rev},purpose:'prod-qualification',environment:'prod',mode:'simulate'})),'Exact current qualification tasks');
|
|
96
99
|
const history=validateHistoricalDevelopmentFiles({root,workspaceRoot,projectRoot:relative,history:input.development}),prerequisiteEvidence=prerequisites(scope,history);
|
|
97
100
|
const committedBefore=captureCommittedContractSource(root,history.aggregate);
|
|
98
101
|
await verifyHistoricalDevelopmentChain(history,root);
|