@enterpriseai/cli 3.20.3 → 3.20.5
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/dist/commands/vertical.d.ts.map +1 -1
- package/dist/commands/vertical.js +32 -7
- package/dist/commands/vertical.js.map +1 -1
- package/dist/lib/api.d.ts +2 -1
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +2 -2
- package/dist/lib/api.js.map +1 -1
- package/package.json +1 -1
- package/resources/deploy/eai-app-template/.github/workflows/eai-app.yml +62 -0
- package/resources/deploy/eai-app-template/producer-pin.json +8 -8
- package/resources/deploy/eai-app-template/scripts/source-unknown-deployment-evidence.mjs +27 -5
- package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +184 -6
- package/resources/linked-sources.json +2 -2
|
@@ -32,6 +32,8 @@ const canonicalWorkflowRelativePath = '.github/workflows/eai-app.yml';
|
|
|
32
32
|
const fixtureDirectory = 'tests/fixtures/source-unknown';
|
|
33
33
|
const canonicalDigest =
|
|
34
34
|
'bce4e34fe5c47377e6a679966a37257d944cd00ccf3c15b4ba437f144ed559ed';
|
|
35
|
+
const currentWorkflowDigest =
|
|
36
|
+
'74ede3c4e1de12fa5f63135349e61365bd845443dbb4dbe51573d8c490baa1d5';
|
|
35
37
|
const generatedDigest =
|
|
36
38
|
'77d4951b3e6852ead73cef8b9e8901e3de774f9aca70f71cc0c932c13903ff32';
|
|
37
39
|
const digest = (bytes) => createHash('sha256').update(bytes).digest('hex');
|
|
@@ -749,7 +751,7 @@ function resolveDeploymentWorkflowContext(
|
|
|
749
751
|
if (!existsSync(manifestPath)) {
|
|
750
752
|
assert.equal(
|
|
751
753
|
digest(actual),
|
|
752
|
-
|
|
754
|
+
currentWorkflowDigest,
|
|
753
755
|
'Template workflow must retain the pinned canonical digest.',
|
|
754
756
|
);
|
|
755
757
|
return {
|
|
@@ -762,7 +764,7 @@ function resolveDeploymentWorkflowContext(
|
|
|
762
764
|
validateCliManifest(manifest);
|
|
763
765
|
assert.equal(
|
|
764
766
|
digest(actual),
|
|
765
|
-
|
|
767
|
+
currentWorkflowDigest,
|
|
766
768
|
'CLI workflow must retain the pinned canonical digest.',
|
|
767
769
|
);
|
|
768
770
|
return {
|
|
@@ -1616,6 +1618,77 @@ test('workflow sends OIDC evidence directly to the canonical PublicAPI route', (
|
|
|
1616
1618
|
}
|
|
1617
1619
|
});
|
|
1618
1620
|
|
|
1621
|
+
test('GitHub attestation availability is exact and leaves signed handoff required', () => {
|
|
1622
|
+
const workflow = readFileSync(workflowPath, 'utf8');
|
|
1623
|
+
const decision = workflow.match(
|
|
1624
|
+
/- name: Check GitHub attestation availability[\s\S]*?run: \|\n node - <<'NODE'\n([\s\S]*?)\n NODE/,
|
|
1625
|
+
);
|
|
1626
|
+
assert.ok(decision);
|
|
1627
|
+
const script = decision[1].replace(/^ {10}/gm, '');
|
|
1628
|
+
assert.match(
|
|
1629
|
+
workflow,
|
|
1630
|
+
/- name: Attest immutable image archive provenance\n if: \$\{\{ steps\.attestation-availability\.outputs\.available == 'true' \}\}/,
|
|
1631
|
+
);
|
|
1632
|
+
assert.match(
|
|
1633
|
+
workflow,
|
|
1634
|
+
/- name: Request GitHub OIDC token and submit workflow evidence\n env:/,
|
|
1635
|
+
);
|
|
1636
|
+
const directory = mkdtempSync(join(tmpdir(), 'eai-attestation-availability-'));
|
|
1637
|
+
const eventPath = join(directory, 'event.json');
|
|
1638
|
+
const outputPath = join(directory, 'output');
|
|
1639
|
+
const repository = {
|
|
1640
|
+
id: 1414837259,
|
|
1641
|
+
full_name: 'customer/app',
|
|
1642
|
+
private: true,
|
|
1643
|
+
owner: { id: 332652381, login: 'customer', type: 'User' },
|
|
1644
|
+
};
|
|
1645
|
+
const check = (candidate) => {
|
|
1646
|
+
writeFileSync(eventPath, JSON.stringify({ repository: candidate }));
|
|
1647
|
+
writeFileSync(outputPath, '');
|
|
1648
|
+
const result = spawnSync(process.execPath, ['-'], {
|
|
1649
|
+
input: script,
|
|
1650
|
+
encoding: 'utf8',
|
|
1651
|
+
env: {
|
|
1652
|
+
...process.env,
|
|
1653
|
+
GITHUB_EVENT_PATH: eventPath,
|
|
1654
|
+
GITHUB_OUTPUT: outputPath,
|
|
1655
|
+
EXPECTED_REPOSITORY: 'customer/app',
|
|
1656
|
+
EXPECTED_REPOSITORY_ID: '1414837259',
|
|
1657
|
+
EXPECTED_OWNER_ID: '332652381',
|
|
1658
|
+
},
|
|
1659
|
+
});
|
|
1660
|
+
return { result, output: readFileSync(outputPath, 'utf8') };
|
|
1661
|
+
};
|
|
1662
|
+
try {
|
|
1663
|
+
const personalPrivate = check(repository);
|
|
1664
|
+
assert.equal(personalPrivate.result.status, 0, personalPrivate.result.stderr);
|
|
1665
|
+
assert.equal(personalPrivate.output, 'available=false\n');
|
|
1666
|
+
for (const supported of [
|
|
1667
|
+
{ ...repository, private: false },
|
|
1668
|
+
{ ...repository, owner: { ...repository.owner, type: 'Organization' } },
|
|
1669
|
+
]) {
|
|
1670
|
+
const outcome = check(supported);
|
|
1671
|
+
assert.equal(outcome.result.status, 0, outcome.result.stderr);
|
|
1672
|
+
assert.equal(outcome.output, 'available=true\n');
|
|
1673
|
+
}
|
|
1674
|
+
for (const invalid of [
|
|
1675
|
+
{ ...repository, private: undefined },
|
|
1676
|
+
{ ...repository, private: 'true' },
|
|
1677
|
+
{ ...repository, id: 3 },
|
|
1678
|
+
{ ...repository, full_name: 'other/app' },
|
|
1679
|
+
{ ...repository, owner: { ...repository.owner, type: undefined } },
|
|
1680
|
+
{ ...repository, owner: { ...repository.owner, id: 3 } },
|
|
1681
|
+
{ ...repository, owner: { ...repository.owner, login: 'other' } },
|
|
1682
|
+
]) {
|
|
1683
|
+
const outcome = check(invalid);
|
|
1684
|
+
assert.notEqual(outcome.result.status, 0);
|
|
1685
|
+
assert.equal(outcome.output, '');
|
|
1686
|
+
}
|
|
1687
|
+
} finally {
|
|
1688
|
+
rmSync(directory, { recursive: true, force: true });
|
|
1689
|
+
}
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1619
1692
|
test('reusable workflow compatibility keeps manual same-repository OIDC authority', () => {
|
|
1620
1693
|
const workflow = readFileSync(workflowPath, 'utf8');
|
|
1621
1694
|
const readme = readFileSync(readmePath, 'utf8');
|
|
@@ -1703,6 +1776,8 @@ test('local CLI dispatch binds direct DEV tunnel, original template and numeric
|
|
|
1703
1776
|
writeFileSync(join(root, '.eai/cli-managed-source-operation.json'), JSON.stringify({
|
|
1704
1777
|
schemaVersion: 'eai.cli_managed_source_operation.v1',
|
|
1705
1778
|
sourceMode: 'eai-cli-generated', operationId, environment: 'dev',
|
|
1779
|
+
tenantId: 'tenant-parent', targetTenantId: 'hosting-tenant', appKey: 'rates-review',
|
|
1780
|
+
repositoryAuthority: 'eai-managed',
|
|
1706
1781
|
templateCommitSha: 'b'.repeat(40),
|
|
1707
1782
|
}));
|
|
1708
1783
|
execFileSync('git', ['init', '-q'], { cwd: root });
|
|
@@ -1720,6 +1795,68 @@ test('local CLI dispatch binds direct DEV tunnel, original template and numeric
|
|
|
1720
1795
|
'--local-e2e-expires-at', expiry, '--github-event-name', 'workflow_dispatch',
|
|
1721
1796
|
'--reusable-call', 'false', '--repository-id', '12345'];
|
|
1722
1797
|
runEvidenceScript(args);
|
|
1798
|
+
writeFileSync(join(root, '.eai/cli-managed-source-operation.json'), JSON.stringify({
|
|
1799
|
+
schemaVersion: 'eai.cli_managed_source_operation.v1', sourceMode: 'eai-cli-generated',
|
|
1800
|
+
operationId, environment: 'dev', tenantId: 'tenant-parent', targetTenantId: 'hosting-tenant',
|
|
1801
|
+
appKey: 'rates-review', repositoryAuthority: 'customer-owned', templateCommitSha: 'b'.repeat(40),
|
|
1802
|
+
}));
|
|
1803
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...args], { encoding: 'utf8' }).status, 1);
|
|
1804
|
+
writeFileSync(join(root, '.eai/cli-managed-source-operation.json'), JSON.stringify({
|
|
1805
|
+
schemaVersion: 'eai.cli_managed_source_operation.v1', sourceMode: 'eai-cli-generated',
|
|
1806
|
+
operationId, environment: 'dev', tenantId: 'tenant-parent', targetTenantId: 'hosting-tenant',
|
|
1807
|
+
appKey: 'rates-review', repositoryAuthority: 'eai-managed', templateCommitSha: 'b'.repeat(40),
|
|
1808
|
+
}));
|
|
1809
|
+
const sameTemplate = [...args, '--local-e2e-template-commit-sha', 'b'.repeat(40)];
|
|
1810
|
+
runEvidenceScript(sameTemplate);
|
|
1811
|
+
const wrongInitialTemplate = [...args, '--local-e2e-template-commit-sha', 'd'.repeat(40)];
|
|
1812
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...wrongInitialTemplate], { encoding: 'utf8' }).status, 1);
|
|
1813
|
+
const predecessorOperationId = `cli-managed-${'e'.repeat(32)}`;
|
|
1814
|
+
const historicalOperationId = `cli-managed-${'9'.repeat(32)}`;
|
|
1815
|
+
const successorMarker = {
|
|
1816
|
+
schemaVersion: 'eai.cli_managed_source_operation.v1',
|
|
1817
|
+
sourceMode: 'eai-cli-generated', operationId: historicalOperationId, environment: 'dev',
|
|
1818
|
+
tenantId: 'tenant-parent', targetTenantId: 'hosting-tenant', appKey: 'rates-review',
|
|
1819
|
+
repositoryAuthority: 'customer-owned', templateCommitSha: 'b'.repeat(40),
|
|
1820
|
+
predecessor: { operationId: predecessorOperationId },
|
|
1821
|
+
};
|
|
1822
|
+
const markerPath = join(root, '.eai/cli-managed-source-operation.json');
|
|
1823
|
+
writeFileSync(markerPath, JSON.stringify(successorMarker));
|
|
1824
|
+
const successor = [...args,
|
|
1825
|
+
'--local-e2e-template-commit-sha', 'd'.repeat(40),
|
|
1826
|
+
'--local-e2e-predecessor-operation-id', predecessorOperationId];
|
|
1827
|
+
runEvidenceScript(successor);
|
|
1828
|
+
runEvidenceScript(['collect', ...successor.slice(1),
|
|
1829
|
+
'--repo', 'customer/rates-review', '--workflow', '.github/workflows/eai-app.yml',
|
|
1830
|
+
'--ref', 'refs/heads/main', '--branch', 'main', '--workflow-run-id', '123456789',
|
|
1831
|
+
'--workflow-run-attempt', '1', '--artifact-id', '987654321',
|
|
1832
|
+
'--artifact-digest', `sha256:${'d'.repeat(64)}`, '--image-digest', `sha256:${'c'.repeat(64)}`]);
|
|
1833
|
+
const collected = JSON.parse(readFileSync(join(root,
|
|
1834
|
+
'.eai-build/evidence/source-unknown-deployment-evidence.json'), 'utf8'));
|
|
1835
|
+
const successorTuple = ['eai.cli-managed-local-e2e-aud.v1',
|
|
1836
|
+
'https://careful-8000.eai.devtunnels.ms', operationId, '12345', 'd'.repeat(40), expiry];
|
|
1837
|
+
assert.equal(collected.localE2e.templateCommitSha, 'd'.repeat(40));
|
|
1838
|
+
assert.equal(collected.localE2e.audience,
|
|
1839
|
+
'api://enterprise-ai-publicapi/eai-cli-generated/local-v1/' + digest(JSON.stringify(successorTuple)));
|
|
1840
|
+
writeFileSync(markerPath, JSON.stringify({ ...successorMarker,
|
|
1841
|
+
predecessor: { operationId: `cli-managed-${'f'.repeat(32)}` } }));
|
|
1842
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...successor], { encoding: 'utf8' }).status, 1);
|
|
1843
|
+
writeFileSync(markerPath, JSON.stringify({ ...successorMarker, repositoryAuthority: 'eai-managed' }));
|
|
1844
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...successor], { encoding: 'utf8' }).status, 1);
|
|
1845
|
+
writeFileSync(markerPath, JSON.stringify(successorMarker));
|
|
1846
|
+
for (const mutated of [
|
|
1847
|
+
[...args],
|
|
1848
|
+
[...successor.slice(0, -1), `cli-managed-${'f'.repeat(32)}`],
|
|
1849
|
+
[...args, '--local-e2e-predecessor-operation-id', predecessorOperationId],
|
|
1850
|
+
[...successor.slice(0, -3), 'not-a-sha', ...successor.slice(-2)],
|
|
1851
|
+
]) {
|
|
1852
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...mutated], { encoding: 'utf8' }).status, 1);
|
|
1853
|
+
}
|
|
1854
|
+
const wrongScope = [...successor];
|
|
1855
|
+
wrongScope[wrongScope.indexOf('--tenant-id') + 1] = 'another-tenant';
|
|
1856
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...wrongScope], { encoding: 'utf8' }).status, 1);
|
|
1857
|
+
const wrongRepository = [...successor];
|
|
1858
|
+
wrongRepository[wrongRepository.indexOf('--repository-id') + 1] = '0';
|
|
1859
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...wrongRepository], { encoding: 'utf8' }).status, 1);
|
|
1723
1860
|
for (const [key, value] of [
|
|
1724
1861
|
['--public-api-url', 'https://attacker.example'],
|
|
1725
1862
|
['--environment', 'test'],
|
|
@@ -1742,12 +1879,44 @@ test('local CLI dispatch binds direct DEV tunnel, original template and numeric
|
|
|
1742
1879
|
const reusableInputs = workflow.slice(workflow.indexOf(' workflow_call:'), workflow.indexOf('\npermissions:'));
|
|
1743
1880
|
assert.match(directInputs, /local_e2e_tunnel:/);
|
|
1744
1881
|
assert.match(directInputs, /local_e2e_expires_at:/);
|
|
1745
|
-
assert.
|
|
1882
|
+
assert.match(directInputs, /local_e2e_template_commit_sha:/);
|
|
1883
|
+
assert.match(directInputs, /local_e2e_predecessor_operation_id:/);
|
|
1884
|
+
assert.doesNotMatch(reusableInputs, /local_e2e_tunnel:|local_e2e_expires_at:|local_e2e_template_commit_sha:|local_e2e_predecessor_operation_id:/);
|
|
1746
1885
|
} finally {
|
|
1747
1886
|
rmSync(root, { recursive: true, force: true });
|
|
1748
1887
|
}
|
|
1749
1888
|
});
|
|
1750
1889
|
|
|
1890
|
+
test('build and OIDC workflow stages enforce the same server-pinned successor template', () => {
|
|
1891
|
+
const workflow = readFileSync(workflowPath, 'utf8');
|
|
1892
|
+
const buildError = workflow.indexOf("throw new Error('Build evidence local successor binding changed.');");
|
|
1893
|
+
const buildStart = workflow.lastIndexOf('if (sourceUnknownLocal', buildError);
|
|
1894
|
+
const buildEnd = workflow.indexOf('\n const tuple =', buildError);
|
|
1895
|
+
const oidcStep = workflow.indexOf('name: Request GitHub OIDC token and submit workflow evidence');
|
|
1896
|
+
const oidcStart = workflow.indexOf('if (sourceUnknownLocal\n', oidcStep);
|
|
1897
|
+
const oidcEnd = workflow.indexOf('\n const expectedKeys', oidcStart);
|
|
1898
|
+
assert.ok(buildError > 0 && buildStart > 0 && buildEnd > buildError);
|
|
1899
|
+
assert.ok(oidcStep > 0 && oidcStart > oidcStep && oidcEnd > oidcStart);
|
|
1900
|
+
for (const guard of [workflow.slice(buildStart, buildEnd), workflow.slice(oidcStart, oidcEnd)]) {
|
|
1901
|
+
const runGuard = (sourceUnknownLocal, localSha, serverSha, predecessorId = '') => runInNewContext(guard, {
|
|
1902
|
+
sourceUnknownLocal,
|
|
1903
|
+
local: { templateCommitSha: localSha },
|
|
1904
|
+
process: {
|
|
1905
|
+
env: {
|
|
1906
|
+
LOCAL_E2E_TEMPLATE_COMMIT_SHA: serverSha,
|
|
1907
|
+
LOCAL_E2E_PREDECESSOR_OPERATION_ID: predecessorId,
|
|
1908
|
+
},
|
|
1909
|
+
exit(code) { throw new Error(`exit ${code}`); },
|
|
1910
|
+
},
|
|
1911
|
+
});
|
|
1912
|
+
assert.doesNotThrow(() => runGuard(false, 'a'.repeat(40), 'a'.repeat(40), `cli-managed-${'b'.repeat(32)}`));
|
|
1913
|
+
assert.throws(() => runGuard(false, 'a'.repeat(40), 'c'.repeat(40), `cli-managed-${'b'.repeat(32)}`));
|
|
1914
|
+
assert.doesNotThrow(() => runGuard(false, 'a'.repeat(40), ''));
|
|
1915
|
+
assert.throws(() => runGuard(true, 'a'.repeat(40), 'c'.repeat(40)));
|
|
1916
|
+
assert.throws(() => runGuard(true, 'a'.repeat(40), '', `cli-managed-${'b'.repeat(32)}`));
|
|
1917
|
+
}
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1751
1920
|
test('local customer-owned source uses its own exact direct-operation audience', () => {
|
|
1752
1921
|
const root = mkdtempSync(join(tmpdir(), 'eai-source-unknown-local-dispatch-'));
|
|
1753
1922
|
try {
|
|
@@ -1768,6 +1937,12 @@ test('local customer-owned source uses its own exact direct-operation audience',
|
|
|
1768
1937
|
'--github-event-name', 'workflow_dispatch', '--reusable-call', 'false',
|
|
1769
1938
|
'--repository-id', '12345'];
|
|
1770
1939
|
runEvidenceScript(args);
|
|
1940
|
+
for (const extra of [
|
|
1941
|
+
['--local-e2e-template-commit-sha', 'd'.repeat(40)],
|
|
1942
|
+
['--local-e2e-predecessor-operation-id', `cli-managed-${'e'.repeat(32)}`],
|
|
1943
|
+
]) {
|
|
1944
|
+
assert.equal(spawnSync(process.execPath, [evidenceScript, ...args, ...extra], { encoding: 'utf8' }).status, 1);
|
|
1945
|
+
}
|
|
1771
1946
|
const withoutTarget = [...args];
|
|
1772
1947
|
withoutTarget.splice(withoutTarget.indexOf('--target-tenant-id'), 2);
|
|
1773
1948
|
runEvidenceScript(withoutTarget);
|
|
@@ -5583,6 +5758,9 @@ syncBuiltinESMExports();
|
|
|
5583
5758
|
const root = repoRoot;
|
|
5584
5759
|
const fixtures = join(root, 'tests/fixtures/source-unknown');
|
|
5585
5760
|
const canonical = regularBytes(workflowPath);
|
|
5761
|
+
const legacyCanonical = existsSync(join(fixtures, 'eai-app.yml'))
|
|
5762
|
+
? regularBytes(join(fixtures, 'eai-app.yml'))
|
|
5763
|
+
: canonical;
|
|
5586
5764
|
// INVARIANT: synthetic controlled input stays inline so legacy four-file repairs execute every control without new fixture dependencies.
|
|
5587
5765
|
const generated = gunzipSync(
|
|
5588
5766
|
Buffer.from(
|
|
@@ -5654,7 +5832,7 @@ function contextFixture(t, manifest, workflow = canonical, operation) {
|
|
|
5654
5832
|
mkdirSync(join(directory, '.github/workflows'), { recursive: true });
|
|
5655
5833
|
mkdirSync(join(directory, 'tests/fixtures'), { recursive: true });
|
|
5656
5834
|
mkdirSync(join(directory, fixtureDirectory), { recursive: true });
|
|
5657
|
-
writeFileSync(join(directory, fixtureDirectory, 'eai-app.yml'),
|
|
5835
|
+
writeFileSync(join(directory, fixtureDirectory, 'eai-app.yml'), legacyCanonical);
|
|
5658
5836
|
writeFileSync(
|
|
5659
5837
|
join(directory, fixtureDirectory, 'generated-eai-app.yml'),
|
|
5660
5838
|
generated,
|
|
@@ -6007,7 +6185,7 @@ test('generated contract fixture drift fails before comparison', () => {
|
|
|
6007
6185
|
test('inline synthetic generated input retains repository and actual normalized export parity', () => {
|
|
6008
6186
|
assert.equal(digest(generated), generatedDigest);
|
|
6009
6187
|
if (existsSync(fixtures)) {
|
|
6010
|
-
assert.
|
|
6188
|
+
assert.equal(digest(regularBytes(join(fixtures, 'eai-app.yml'))), canonicalDigest);
|
|
6011
6189
|
assert.ok(
|
|
6012
6190
|
regularBytes(join(fixtures, 'generated-eai-app.yml')).equals(generated),
|
|
6013
6191
|
);
|
|
@@ -6023,7 +6201,7 @@ test('inline synthetic generated input retains repository and actual normalized
|
|
|
6023
6201
|
);
|
|
6024
6202
|
assert.equal(
|
|
6025
6203
|
digest(regularBytes(join(root, canonicalWorkflowRelativePath))),
|
|
6026
|
-
|
|
6204
|
+
currentWorkflowDigest,
|
|
6027
6205
|
);
|
|
6028
6206
|
}
|
|
6029
6207
|
});
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
},
|
|
8
8
|
"appTemplate": {
|
|
9
9
|
"repo": "https://github.com/eai-support/eai-app-template.git",
|
|
10
|
-
"version": "v1.0.
|
|
11
|
-
"commit": "
|
|
10
|
+
"version": "v1.0.17",
|
|
11
|
+
"commit": "3162df8dffd36465ef2cec474eee4f8e6285ec3c",
|
|
12
12
|
"packageLockSha256": "5b04e0be44ef93a817e4dc5076914d0f0684712b41155ee2934ce42f184df415",
|
|
13
13
|
"enterpriseRegistry": "https://enterpriseaigroup.github.io/enterpriseai-packages/registry",
|
|
14
14
|
"enterprisePackageFingerprint": "3286fdf91d6a25a55a4d84de21bf5c592bcf11abffb96dc2be90e93414eeb646",
|