feat(contracts): share notification, organization and device capability models
Move the Hub channel table, organization tags and device capability definitions into the workspace packages so the control plane and the console validate the same contract, and refresh the frozen upstream evidence for the new paths.
This commit is contained in:
@@ -3,67 +3,477 @@ import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { upstream58e2204Operations } from '../src/upstream-58e2204.ts';
|
||||
import { operationAcceptance58e2204, operationAcceptanceOverrides58e2204, acceptancePolicyCatalog, controlPlaneAcceptance, fixtureDisposition58e2204, renderOperationAcceptanceMatrix, surfaceGroups58e2204, policyGroups58e2204, scenarioGroups58e2204, availabilityGroups58e2204 } from '../src/acceptance-58e2204.ts';
|
||||
import {
|
||||
operationAcceptance58e2204,
|
||||
operationAcceptanceOverrides58e2204,
|
||||
acceptancePolicyCatalog,
|
||||
controlPlaneAcceptance,
|
||||
fixtureDisposition58e2204,
|
||||
renderOperationAcceptanceMatrix,
|
||||
surfaceGroups58e2204,
|
||||
policyGroups58e2204,
|
||||
scenarioGroups58e2204,
|
||||
availabilityGroups58e2204,
|
||||
} from '../src/acceptance-58e2204.ts';
|
||||
import { DENY_REASONS, selectReadonlyOperations } from '../../test-fixtures/scripts/collector.ts';
|
||||
|
||||
const fixtureManifestPath=fileURLToPath(new URL('../../test-fixtures/src/manifest.json',import.meta.url));
|
||||
const matrixPath=fileURLToPath(new URL('../../../docs/product/operation-acceptance-matrix.md',import.meta.url));
|
||||
const acceptanceSourcePath=fileURLToPath(new URL('../src/acceptance-58e2204.ts',import.meta.url));
|
||||
const iaPath=fileURLToPath(new URL('../../../docs/product/information-architecture.md',import.meta.url));
|
||||
const manifest=JSON.parse(await readFile(fixtureManifestPath,'utf8'));
|
||||
const byId=new Map(upstream58e2204Operations.map((o:any)=>[o.operationId,o]));
|
||||
const canonicalRoutes=new Set(['/fleet','/instances/new','/instances/:id/overview','/instances/:id/cellular','/instances/:id/device-network','/instances/:id/messages','/instances/:id/calls','/instances/:id/esim','/instances/:id/notifications','/instances/:id/automation','/instances/:id/ota','/settings/instances/:id']);
|
||||
const assertExactPartition=(groups:readonly {ids:readonly string[]}[])=>{const ids=groups.flatMap(g=>[...g.ids]);assert.equal(ids.length,117);assert.equal(new Set(ids).size,117);assert.deepEqual([...ids].sort(),[...byId.keys()].sort());};
|
||||
const fixtureManifestPath = fileURLToPath(
|
||||
new URL('../../test-fixtures/src/manifest.json', import.meta.url),
|
||||
);
|
||||
const matrixPath = fileURLToPath(
|
||||
new URL('../../../docs/product/operation-acceptance-matrix.md', import.meta.url),
|
||||
);
|
||||
const acceptanceSourcePath = fileURLToPath(
|
||||
new URL('../src/acceptance-58e2204.ts', import.meta.url),
|
||||
);
|
||||
const iaPath = fileURLToPath(
|
||||
new URL('../../../docs/product/information-architecture.md', import.meta.url),
|
||||
);
|
||||
const manifest = JSON.parse(await readFile(fixtureManifestPath, 'utf8'));
|
||||
const byId = new Map(upstream58e2204Operations.map((o: any) => [o.operationId, o]));
|
||||
const canonicalRoutes = new Set([
|
||||
'/fleet',
|
||||
'/instances/new',
|
||||
'/instances/:id/overview',
|
||||
'/instances/:id/cellular',
|
||||
'/instances/:id/device-network',
|
||||
'/instances/:id/messages',
|
||||
'/instances/:id/calls',
|
||||
'/instances/:id/esim',
|
||||
'/instances/:id/notifications',
|
||||
'/instances/:id/automation',
|
||||
'/instances/:id/ota',
|
||||
'/settings/instances/:id',
|
||||
]);
|
||||
const assertExactPartition = (groups: readonly { ids: readonly string[] }[]) => {
|
||||
const ids = groups.flatMap((g) => [...g.ids]);
|
||||
assert.equal(ids.length, 117);
|
||||
assert.equal(new Set(ids).size, 117);
|
||||
assert.deepEqual([...ids].sort(), [...byId.keys()].sort());
|
||||
};
|
||||
|
||||
test('RED→GREEN: surface/policy/scenario/availability are literal exact operation partitions',()=>{
|
||||
for(const groups of [surfaceGroups58e2204,policyGroups58e2204,scenarioGroups58e2204,availabilityGroups58e2204])assertExactPartition(groups);
|
||||
assert.ok(surfaceGroups58e2204.filter(g=>g.surfaceId.startsWith('calls/')).length>=5);
|
||||
assert.ok(surfaceGroups58e2204.filter(g=>g.surfaceId.startsWith('cellular/')).length>=5);
|
||||
for(const group of surfaceGroups58e2204)for(const id of group.ids){const row=operationAcceptance58e2204.find(x=>x.operationId===id)!;assert.equal(row.surfaceId,group.surfaceId);assert.equal(row.primaryRoute,group.primaryRoute);}
|
||||
const tuples=new Set(policyGroups58e2204.map(g=>Object.values(g.policies).join('/')));assert.ok(policyGroups58e2204.length>=8);assert.ok(tuples.size>=8);
|
||||
const policy=(id:string)=>policyGroups58e2204.find(g=>g.ids.includes(id))!;assert.match(policy('getSmsList').groupId,/list/);assert.match(policy('getHealth').groupId,/detail/);assert.match(policy('getNetworkOperatorsScan').groupId,/scan/);assert.match(policy('postData').groupId,/direct/);assert.match(policy('postAuthLogin').groupId,/auth/);assert.match(policy('postSmsBatchDelete').groupId,/destructive/);assert.match(policy('postOtaApply').groupId,/ota/);
|
||||
assert.ok(scenarioGroups58e2204.length>=8);const scenario=(id:string)=>scenarioGroups58e2204.find(g=>g.ids.includes(id))!.scenarios;assert.equal(scenario('getHealth').empty.applicable,false);assert.equal(scenario('getSmsList').empty.applicable,true);assert.equal(scenario('getStats').partial.applicable,true);assert.equal(scenario('getAuthStatus').partial.applicable,false);assert.equal(scenario('getNetworkOperatorsScan').empty.applicable,true);assert.equal(scenario('getNetworkOperatorsScan').partial.applicable,true);assert.equal(scenario('getNetworkOperatorsScan')['policy-forbidden'].applicable,true);assert.equal(scenario('getNetworkOperatorsScan')['unknown-result'].applicable,false);for(const id of ['postSmsBatchDelete','postNotificationsQueueRetryAll','postNotificationsQueueClear'])assert.equal(scenario(id).partial.applicable,true,id);for(const id of ['postSystemReboot','postServiceRestart','deleteCallHistoryId','postBandLock','postOtaApply','postOtaUpload','postOtaOnlinePrepare','postOtaLatestRelease','postOtaCancel','postData'])assert.equal(scenario(id).partial.applicable,false,id);for(const id of ['postData','postSmsBatchDelete']){assert.equal(scenario(id)['policy-forbidden'].applicable,true);assert.equal(scenario(id)['unknown-result'].applicable,true);}for(const id of ['getHealth','getSmsList']){assert.equal(scenario(id)['policy-forbidden'].applicable,false);assert.equal(scenario(id)['unknown-result'].applicable,false);}for(const group of scenarioGroups58e2204)assert.equal(group.scenarios['owner-switch'].applicable,true);
|
||||
assert.ok(availabilityGroups58e2204.length>=4);for(const group of availabilityGroups58e2204)assert.ok(group.reason.length>20);
|
||||
test('RED→GREEN: surface/policy/scenario/availability are literal exact operation partitions', () => {
|
||||
for (const groups of [
|
||||
surfaceGroups58e2204,
|
||||
policyGroups58e2204,
|
||||
scenarioGroups58e2204,
|
||||
availabilityGroups58e2204,
|
||||
])
|
||||
assertExactPartition(groups);
|
||||
assert.ok(surfaceGroups58e2204.filter((g) => g.surfaceId.startsWith('calls/')).length >= 5);
|
||||
assert.ok(surfaceGroups58e2204.filter((g) => g.surfaceId.startsWith('cellular/')).length >= 5);
|
||||
for (const group of surfaceGroups58e2204)
|
||||
for (const id of group.ids) {
|
||||
const row = operationAcceptance58e2204.find((x) => x.operationId === id)!;
|
||||
assert.equal(row.surfaceId, group.surfaceId);
|
||||
assert.equal(row.primaryRoute, group.primaryRoute);
|
||||
}
|
||||
const tuples = new Set(policyGroups58e2204.map((g) => Object.values(g.policies).join('/')));
|
||||
assert.ok(policyGroups58e2204.length >= 8);
|
||||
assert.ok(tuples.size >= 8);
|
||||
const policy = (id: string) => policyGroups58e2204.find((g) => g.ids.includes(id))!;
|
||||
assert.match(policy('getSmsList').groupId, /list/);
|
||||
assert.match(policy('getHealth').groupId, /detail/);
|
||||
assert.match(policy('getNetworkOperatorsScan').groupId, /scan/);
|
||||
assert.match(policy('postData').groupId, /direct/);
|
||||
assert.match(policy('postAuthLogin').groupId, /auth/);
|
||||
assert.match(policy('postSmsBatchDelete').groupId, /destructive/);
|
||||
assert.match(policy('postOtaApply').groupId, /ota/);
|
||||
assert.ok(scenarioGroups58e2204.length >= 8);
|
||||
const scenario = (id: string) => scenarioGroups58e2204.find((g) => g.ids.includes(id))!.scenarios;
|
||||
assert.equal(scenario('getHealth').empty.applicable, false);
|
||||
assert.equal(scenario('getSmsList').empty.applicable, true);
|
||||
assert.equal(scenario('getStats').partial.applicable, true);
|
||||
assert.equal(scenario('getAuthStatus').partial.applicable, false);
|
||||
assert.equal(scenario('getNetworkOperatorsScan').empty.applicable, true);
|
||||
assert.equal(scenario('getNetworkOperatorsScan').partial.applicable, true);
|
||||
assert.equal(scenario('getNetworkOperatorsScan')['policy-forbidden'].applicable, true);
|
||||
assert.equal(scenario('getNetworkOperatorsScan')['unknown-result'].applicable, false);
|
||||
for (const id of [
|
||||
'postSmsBatchDelete',
|
||||
'postNotificationsQueueRetryAll',
|
||||
'postNotificationsQueueClear',
|
||||
])
|
||||
assert.equal(scenario(id).partial.applicable, true, id);
|
||||
for (const id of [
|
||||
'postSystemReboot',
|
||||
'postServiceRestart',
|
||||
'deleteCallHistoryId',
|
||||
'postBandLock',
|
||||
'postOtaApply',
|
||||
'postOtaUpload',
|
||||
'postOtaOnlinePrepare',
|
||||
'postOtaLatestRelease',
|
||||
'postOtaCancel',
|
||||
'postData',
|
||||
])
|
||||
assert.equal(scenario(id).partial.applicable, false, id);
|
||||
for (const id of ['postData', 'postSmsBatchDelete']) {
|
||||
assert.equal(scenario(id)['policy-forbidden'].applicable, true);
|
||||
assert.equal(scenario(id)['unknown-result'].applicable, true);
|
||||
}
|
||||
for (const id of ['getHealth', 'getSmsList']) {
|
||||
assert.equal(scenario(id)['policy-forbidden'].applicable, false);
|
||||
assert.equal(scenario(id)['unknown-result'].applicable, false);
|
||||
}
|
||||
for (const group of scenarioGroups58e2204)
|
||||
assert.equal(group.scenarios['owner-switch'].applicable, true);
|
||||
assert.ok(availabilityGroups58e2204.length >= 4);
|
||||
for (const group of availabilityGroups58e2204) assert.ok(group.reason.length > 20);
|
||||
});
|
||||
|
||||
test('RED→GREEN: acceptance ledger is exact, Registry-bound and IA-owned',()=>{
|
||||
assert.equal(operationAcceptance58e2204.length,117);
|
||||
assert.equal(new Set(operationAcceptance58e2204.map(x=>x.operationId)).size,117);
|
||||
assert.deepEqual([...operationAcceptance58e2204.map(x=>x.operationId)].sort(),[...byId.keys()].sort());
|
||||
for(const row of operationAcceptance58e2204){const op:any=byId.get(row.operationId);assert.ok(op);assert.equal(row.method,op.method);assert.equal(row.pathTemplate,op.pathTemplate);assert.equal(row.upstreamDomain,op.upstreamDomain);assert.equal(row.riskLevel,op.riskLevel);assert.equal(row.confirmationUX,op.confirmationPolicy);assert.ok(canonicalRoutes.has(row.primaryRoute),row.operationId);assert.ok(row.surfaceId);assert.ok(row.uiStrategy);assert.match(row.availability,/^(planned|unsupported-version|deferred-with-reason)$/);if(row.availability==='deferred-with-reason')assert.ok(row.availabilityReason);assert.ok(row.requiredStates.request.length&&row.requiredStates.freshness.length&&row.requiredStates.support.length);assert.ok(row.requiredStates.scenarios.length||row.requiredStates.naRationale);assert.ok(row.evidenceIds.length);}
|
||||
test('RED→GREEN: acceptance ledger is exact, Registry-bound and IA-owned', () => {
|
||||
assert.equal(operationAcceptance58e2204.length, 117);
|
||||
assert.equal(new Set(operationAcceptance58e2204.map((x) => x.operationId)).size, 117);
|
||||
assert.deepEqual(
|
||||
[...operationAcceptance58e2204.map((x) => x.operationId)].sort(),
|
||||
[...byId.keys()].sort(),
|
||||
);
|
||||
for (const row of operationAcceptance58e2204) {
|
||||
const op: any = byId.get(row.operationId);
|
||||
assert.ok(op);
|
||||
assert.equal(row.method, op.method);
|
||||
assert.equal(row.pathTemplate, op.pathTemplate);
|
||||
assert.equal(row.upstreamDomain, op.upstreamDomain);
|
||||
assert.equal(row.riskLevel, op.riskLevel);
|
||||
assert.equal(row.confirmationUX, op.confirmationPolicy);
|
||||
assert.ok(canonicalRoutes.has(row.primaryRoute), row.operationId);
|
||||
assert.ok(row.surfaceId);
|
||||
assert.ok(row.uiStrategy);
|
||||
assert.match(row.availability, /^(planned|unsupported-version|deferred-with-reason)$/);
|
||||
if (row.availability === 'deferred-with-reason') assert.ok(row.availabilityReason);
|
||||
assert.ok(
|
||||
row.requiredStates.request.length &&
|
||||
row.requiredStates.freshness.length &&
|
||||
row.requiredStates.support.length,
|
||||
);
|
||||
assert.ok(row.requiredStates.scenarios.length || row.requiredStates.naRationale);
|
||||
assert.ok(row.evidenceIds.length);
|
||||
}
|
||||
});
|
||||
|
||||
test('RED→GREEN: exact overrides and policy-bound acceptance are auditable',()=>{
|
||||
assert.deepEqual(Object.keys(operationAcceptanceOverrides58e2204).sort(),[...byId.keys()].sort());
|
||||
const policyFields=['preconditionPolicyId','stalePolicyId','unsupportedPolicyId','retryPolicyId','resultPolicyId'];
|
||||
for(const row of operationAcceptance58e2204){const override:any=(operationAcceptanceOverrides58e2204 as any)[row.operationId];assert.ok(override);assert.ok(row.availabilityReason?.length>20,row.operationId);assert.ok(row.versionEvidencePolicy?.length>20,row.operationId);assert.equal(row.surfaceId,override.surfaceId);assert.equal(row.uiStrategy,override.uiStrategy);for(const scenario of ['empty','partial','policy-forbidden','unknown-result','owner-switch']){assert.equal(typeof row.scenarioAcceptance[scenario].applicable,'boolean');assert.ok(row.scenarioAcceptance[scenario].rationale.length>8);}for(const field of policyFields){assert.equal((row as any)[field],override[field]);assert.ok((acceptancePolicyCatalog as any)[field][(row as any)[field]],`${row.operationId}:${field}`);}}
|
||||
test('RED→GREEN: exact overrides and policy-bound acceptance are auditable', () => {
|
||||
assert.deepEqual(
|
||||
Object.keys(operationAcceptanceOverrides58e2204).sort(),
|
||||
[...byId.keys()].sort(),
|
||||
);
|
||||
const policyFields = [
|
||||
'preconditionPolicyId',
|
||||
'stalePolicyId',
|
||||
'unsupportedPolicyId',
|
||||
'retryPolicyId',
|
||||
'resultPolicyId',
|
||||
];
|
||||
for (const row of operationAcceptance58e2204) {
|
||||
const override: any = (operationAcceptanceOverrides58e2204 as any)[row.operationId];
|
||||
assert.ok(override);
|
||||
assert.ok(row.availabilityReason?.length > 20, row.operationId);
|
||||
assert.ok(row.versionEvidencePolicy?.length > 20, row.operationId);
|
||||
assert.equal(row.surfaceId, override.surfaceId);
|
||||
assert.equal(row.uiStrategy, override.uiStrategy);
|
||||
for (const scenario of [
|
||||
'empty',
|
||||
'partial',
|
||||
'policy-forbidden',
|
||||
'unknown-result',
|
||||
'owner-switch',
|
||||
]) {
|
||||
assert.equal(typeof row.scenarioAcceptance[scenario].applicable, 'boolean');
|
||||
assert.ok(row.scenarioAcceptance[scenario].rationale.length > 8);
|
||||
}
|
||||
for (const field of policyFields) {
|
||||
assert.equal((row as any)[field], override[field]);
|
||||
assert.ok(
|
||||
(acceptancePolicyCatalog as any)[field][(row as any)[field]],
|
||||
`${row.operationId}:${field}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('real acceptance is an explicit availability decision coherent with fixture disposition',async()=>{const fixture=new Map(fixtureDisposition58e2204.map(x=>[x.operationId,x]));const valid=new Set(['REAL_READ','REAL_READ_DEFERRED','REAL_WRITE_LATER','SIMULATED_HIGH_RISK','CONTRACT_ONLY']);for(const group of availabilityGroups58e2204){assert.ok(valid.has(group.realAcceptance),group.groupId);for(const id of group.ids){const row=operationAcceptance58e2204.find(x=>x.operationId===id)!;assert.equal(row.realAcceptance,group.realAcceptance,id);const status=fixture.get(id)!.runtimeFixtureStatus;if(status==='captured-readonly')assert.equal(group.realAcceptance,'REAL_READ',id);if(status==='denied-readonly-candidate')assert.equal(group.realAcceptance,'REAL_READ_DEFERRED',id);if(group.realAcceptance==='REAL_READ')assert.equal(status,'captured-readonly',id);if(group.realAcceptance==='REAL_READ_DEFERRED')assert.equal(status,'denied-readonly-candidate',id);}}const source=await readFile(acceptanceSourcePath,'utf8');assert.doesNotMatch(source,/function\s+realAcceptance\s*\(/);assert.doesNotMatch(source,/realAcceptanceIntent/);assert.equal(operationAcceptance58e2204.filter(x=>x.realAcceptance==='REAL_READ').length,39);assert.equal(operationAcceptance58e2204.filter(x=>x.realAcceptance==='REAL_READ_DEFERRED').length,11);});
|
||||
|
||||
test('Phase 1 bootstrap gate defers Fleet component/E2E evidence to the Phase 5 implementation gate',async()=>{const ia=await readFile(iaPath,'utf8');const bootstrap=ia.match(/### Phase 0 → Phase 1 workspace\/contract bootstrap gate([\s\S]*?)(?=### Phase 5 implementation gate)/)?.[1];const phase5=ia.match(/### Phase 5 implementation gate([\s\S]*)/)?.[1];assert.ok(bootstrap);assert.ok(phase5);assert.doesNotMatch(bootstrap!,/Fleet[^\n]*组件\/E2E/);assert.match(phase5!,/Fleet[^\n]*组件\/E2E/);assert.match(phase5!,/\[ \][^\n]*Fleet/);assert.match(bootstrap!,/\[x\][^\n]*最终独立规格与质量\/安全复审均已通过/);assert.doesNotMatch(bootstrap!,/PENDING final independent review/);assert.match(ia,/E2E[^\n]*N\/A[^\n]*Phase 5/);});
|
||||
|
||||
test('control-plane flows are independent, structured, and preserve safety invariants',()=>{const ids=['instance-create','instance-update','instance-delete','secret-set','secret-preserve','secret-clear','config-import-preview','config-import-confirm','credential-verify','saved-secret-login','temporary-secret-login','logout','401-recovery','auth-setup','auth-password-change','auth-settings-read','auth-settings-write','job-cancel','job-retry','audit-export','system-settings-update'];assert.deepEqual(controlPlaneAcceptance.map(x=>x.flowId).sort(),ids.sort());for(const flow of controlPlaneAcceptance)for(const field of ['route','risk','riskSubtype','confirmation','preconditions','result','failureRecovery','secretPolicy','evidence'])assert.ok(String((flow as any)[field]).length>1,`${flow.flowId}:${field}`);assert.match(controlPlaneAcceptance.find(x=>x.flowId==='instance-delete')!.result,/new jobId.*two-phase/i);for(const id of ['secret-set','secret-preserve','secret-clear'])assert.match(controlPlaneAcceptance.find(x=>x.flowId===id)!.secretPolicy,/never.*value/i);for(const id of ['saved-secret-login','temporary-secret-login'])assert.match(controlPlaneAcceptance.find(x=>x.flowId===id)!.failureRecovery,/no automatic replay/i);assert.match(controlPlaneAcceptance.find(x=>x.flowId==='job-retry')!.result,/new jobId.*lineage/i);});
|
||||
|
||||
test('product risk prose has no conflicting action summaries',async()=>{const root=fileURLToPath(new URL('../../../docs/product/',import.meta.url));const names=['project-charter.md','personas-and-workflows.md','information-architecture.md','current-system-audit.md'];const docs=(await Promise.all(names.map(n=>readFile(`${root}${n}`,'utf8')))).join('\n');for(const expected of [/notifications config[^\n]*R2[^\n]*Job/i,/automation config[^\n]*R2[^\n]*Job/i,/eSIM[^\n]*download[^\n]*R1[^\n]*direct/i,/WLAN connect[^\n]*R1[^\n]*forget[^\n]*R2/i,/DDNS config[^\n]*R2[^\n]*Job/i,/baseband restart[^\n]*R3[^\n]*status[^\n]*R0/i])assert.match(docs,expected);assert.doesNotMatch(docs,/Notifications[^\n]*config R1/i);assert.doesNotMatch(docs,/Automation[^\n]*config R1/i);});
|
||||
test('R2/R3, dedicated auth, write strategy, and split bulk/fleet policy are gated',()=>{
|
||||
for(const row of operationAcceptance58e2204){const op:any=byId.get(row.operationId);if(['R2','R3'].includes(row.riskLevel)){assert.equal(row.executionMode,op.executionPolicy==='dedicatedFlow'?'dedicated-flow':'preparation-job');assert.match(row.preconditions,/fresh preflight/i);assert.match(row.confirmationUX,/explicit|strong/);assert.match(row.resultDestination,/jobs\/:jobId/);assert.match(row.retryRecovery,/new Job lineage/i);}if(row.riskLevel==='R3')assert.equal(op.capability,'job');if(row.method!=='GET'){assert.notEqual(row.uiStrategy,'read-panel');assert.notEqual(row.realAcceptance,'REAL_READ');assert.equal(row.fleetBatchable,false);}}
|
||||
const auth=operationAcceptance58e2204.filter(x=>['postAuthSetup','postAuthPassword','postAuthSettings','postAuthLogin','postAuthLogout'].includes(x.operationId));assert.equal(auth.length,5);assert.ok(auth.every(x=>x.uiStrategy==='dedicated-auth-flow'&&x.executionMode==='dedicated-flow'));for(const id of ['postAuthLogin','postAuthLogout']){const row=auth.find(x=>x.operationId===id)!;assert.equal(row.sessionSubtype,'session-sensitive');assert.match(row.retryRecovery,/no automatic replay/i);assert.match(row.resultDestination,/metadata-only audit/i);}
|
||||
const sms=operationAcceptance58e2204.find(x=>x.operationId==='postSmsBatchDelete')!;assert.equal(sms.resourceBulk,true);assert.equal(sms.fleetBatchable,false);for(const id of ['postNotificationsQueueRetryAll','postNotificationsQueueClear'])assert.equal(operationAcceptance58e2204.find(x=>x.operationId===id)!.resourceBulk,true,id);for(const id of ['postSmsClear','postCallHistoryClear','postNotificationsLogsClear','postAutomationLogsClear'])assert.equal(operationAcceptance58e2204.find(x=>x.operationId===id)!.resourceBulk,false,id);
|
||||
const health=operationAcceptance58e2204.find(x=>x.operationId==='getHealth')!;assert.equal(health.fleetBatchable,true);assert.equal(health.partialAggregationPolicy,'per-item');
|
||||
test('real acceptance is an explicit availability decision coherent with fixture disposition', async () => {
|
||||
const fixture = new Map(fixtureDisposition58e2204.map((x) => [x.operationId, x]));
|
||||
const valid = new Set([
|
||||
'REAL_READ',
|
||||
'REAL_READ_DEFERRED',
|
||||
'REAL_WRITE_LATER',
|
||||
'SIMULATED_HIGH_RISK',
|
||||
'CONTRACT_ONLY',
|
||||
]);
|
||||
for (const group of availabilityGroups58e2204) {
|
||||
assert.ok(valid.has(group.realAcceptance), group.groupId);
|
||||
for (const id of group.ids) {
|
||||
const row = operationAcceptance58e2204.find((x) => x.operationId === id)!;
|
||||
assert.equal(row.realAcceptance, group.realAcceptance, id);
|
||||
const status = fixture.get(id)!.runtimeFixtureStatus;
|
||||
if (status === 'captured-readonly') assert.equal(group.realAcceptance, 'REAL_READ', id);
|
||||
if (status === 'denied-readonly-candidate')
|
||||
assert.equal(group.realAcceptance, 'REAL_READ_DEFERRED', id);
|
||||
if (group.realAcceptance === 'REAL_READ') assert.equal(status, 'captured-readonly', id);
|
||||
if (group.realAcceptance === 'REAL_READ_DEFERRED')
|
||||
assert.equal(status, 'denied-readonly-candidate', id);
|
||||
}
|
||||
}
|
||||
const source = await readFile(acceptanceSourcePath, 'utf8');
|
||||
assert.doesNotMatch(source, /function\s+realAcceptance\s*\(/);
|
||||
assert.doesNotMatch(source, /realAcceptanceIntent/);
|
||||
assert.equal(
|
||||
operationAcceptance58e2204.filter((x) => x.realAcceptance === 'REAL_READ').length,
|
||||
39,
|
||||
);
|
||||
assert.equal(
|
||||
operationAcceptance58e2204.filter((x) => x.realAcceptance === 'REAL_READ_DEFERRED').length,
|
||||
11,
|
||||
);
|
||||
});
|
||||
|
||||
test('fixture disposition is authoritative and exactly follows collector plus 78-file manifest',()=>{
|
||||
assert.equal(fixtureDisposition58e2204.length,117);assert.equal(new Set(fixtureDisposition58e2204.map(x=>x.operationId)).size,117);
|
||||
const selected=selectReadonlyOperations(upstream58e2204Operations as any[]);assert.equal(selected.selected.length,39);assert.equal(selected.denied.length,11);assert.equal(Object.keys(DENY_REASONS).length,11);
|
||||
const selectedIds=new Set(selected.selected.map((x:any)=>x.operationId));const denied=new Map(selected.denied.map((x:any)=>[x.operationId,x.denyReason]));
|
||||
const filesById=new Map<string,any[]>();for(const file of manifest.files){const match=/instance-[12]--([^.]+)\.json$/.exec(file.path);assert.ok(match);const list=filesById.get(match[1])??[];list.push(file);filesById.set(match[1],list);}
|
||||
for(const d of fixtureDisposition58e2204){if(selectedIds.has(d.operationId)){assert.equal(d.runtimeFixtureStatus,'captured-readonly');assert.equal(d.fixtureCount,2);assert.equal(filesById.get(d.operationId)?.length,2);assert.deepEqual(d.aliases,['instance-1','instance-2']);assert.ok(d.observedCategories.length);}else if(denied.has(d.operationId)){assert.equal(d.runtimeFixtureStatus,'denied-readonly-candidate');assert.equal(d.reason,denied.get(d.operationId));assert.equal(d.fixtureCount,0);}else {assert.match(d.runtimeFixtureStatus,/^not-eligible-readonly-capture/);assert.equal(d.fixtureCount,0);}}
|
||||
assert.equal(manifest.realFixtureCount,78);
|
||||
test('Phase 1 bootstrap gate defers Fleet component/E2E evidence to the Phase 5 implementation gate', async () => {
|
||||
const ia = await readFile(iaPath, 'utf8');
|
||||
const bootstrap = ia.match(
|
||||
/### Phase 0 → Phase 1 workspace\/contract bootstrap gate([\s\S]*?)(?=### Phase 5 implementation gate)/,
|
||||
)?.[1];
|
||||
const phase5 = ia.match(/### Phase 5 implementation gate([\s\S]*)/)?.[1];
|
||||
assert.ok(bootstrap);
|
||||
assert.ok(phase5);
|
||||
assert.doesNotMatch(bootstrap!, /Fleet[^\n]*组件\/E2E/);
|
||||
assert.match(phase5!, /Fleet[^\n]*组件\/E2E/);
|
||||
assert.match(phase5!, /\[ \][^\n]*Fleet/);
|
||||
assert.match(bootstrap!, /\[x\][^\n]*最终独立规格与质量\/安全复审均已通过/);
|
||||
assert.doesNotMatch(bootstrap!, /PENDING final independent review/);
|
||||
assert.match(ia, /E2E[^\n]*N\/A[^\n]*Phase 5/);
|
||||
});
|
||||
|
||||
test('product risk prose defers to Registry and known conflicts stay corrected',async()=>{const productRoot=fileURLToPath(new URL('../../../docs/product/',import.meta.url));const workflows=await readFile(`${productRoot}personas-and-workflows.md`,'utf8');const charter=await readFile(`${productRoot}project-charter.md`,'utf8');assert.doesNotMatch(workflows,/下载 profile[^\n]*R2 Job/);assert.match(workflows,/WLAN connect 为 R1/);assert.match(workflows,/notifications config 为 R2 Job/);assert.match(workflows,/fleetBatchable=true/);assert.match(charter,/resourceBulk.*fleetBatchable/);for(const id of ['postEsimProfiles','postEsimProfilesIccidEnable','postDeviceNetworkWlanConnect','postNotificationsConfig','postAutomationConfig','postDeviceNetworkDdnsConfig','postBasebandRestart']){assert.ok(operationAcceptance58e2204.find(x=>x.operationId===id),id);}});
|
||||
test('control-plane flows are independent, structured, and preserve safety invariants', () => {
|
||||
const ids = [
|
||||
'instance-create',
|
||||
'instance-update',
|
||||
'instance-delete',
|
||||
'secret-set',
|
||||
'secret-preserve',
|
||||
'secret-clear',
|
||||
'config-import-preview',
|
||||
'config-import-confirm',
|
||||
'credential-verify',
|
||||
'saved-secret-login',
|
||||
'temporary-secret-login',
|
||||
'logout',
|
||||
'401-recovery',
|
||||
'auth-setup',
|
||||
'auth-password-change',
|
||||
'auth-settings-read',
|
||||
'auth-settings-write',
|
||||
'job-cancel',
|
||||
'job-retry',
|
||||
'audit-export',
|
||||
'system-settings-update',
|
||||
];
|
||||
assert.deepEqual(controlPlaneAcceptance.map((x) => x.flowId).sort(), ids.sort());
|
||||
for (const flow of controlPlaneAcceptance)
|
||||
for (const field of [
|
||||
'route',
|
||||
'risk',
|
||||
'riskSubtype',
|
||||
'confirmation',
|
||||
'preconditions',
|
||||
'result',
|
||||
'failureRecovery',
|
||||
'secretPolicy',
|
||||
'evidence',
|
||||
])
|
||||
assert.ok(String((flow as any)[field]).length > 1, `${flow.flowId}:${field}`);
|
||||
assert.match(
|
||||
controlPlaneAcceptance.find((x) => x.flowId === 'instance-delete')!.result,
|
||||
/new jobId.*two-phase/i,
|
||||
);
|
||||
for (const id of ['secret-set', 'secret-preserve', 'secret-clear'])
|
||||
assert.match(
|
||||
controlPlaneAcceptance.find((x) => x.flowId === id)!.secretPolicy,
|
||||
/never.*value/i,
|
||||
);
|
||||
for (const id of ['saved-secret-login', 'temporary-secret-login'])
|
||||
assert.match(
|
||||
controlPlaneAcceptance.find((x) => x.flowId === id)!.failureRecovery,
|
||||
/no automatic replay/i,
|
||||
);
|
||||
assert.match(
|
||||
controlPlaneAcceptance.find((x) => x.flowId === 'job-retry')!.result,
|
||||
/new jobId.*lineage/i,
|
||||
);
|
||||
});
|
||||
|
||||
test('generated matrix is synchronized and contains each operation exactly once',async()=>{const doc=await readFile(matrixPath,'utf8');assert.equal(doc,renderOperationAcceptanceMatrix());const ids=[...doc.matchAll(/^\| `([^`]+)` \|/gm)].map(x=>x[1]).filter(id=>byId.has(id));assert.equal(ids.length,117);assert.deepEqual(ids.sort(),[...byId.keys()].sort());});
|
||||
test('product risk prose has no conflicting action summaries', async () => {
|
||||
const root = fileURLToPath(new URL('../../../docs/product/', import.meta.url));
|
||||
const names = [
|
||||
'project-charter.md',
|
||||
'personas-and-workflows.md',
|
||||
'information-architecture.md',
|
||||
'current-system-audit.md',
|
||||
];
|
||||
const docs = (await Promise.all(names.map((n) => readFile(`${root}${n}`, 'utf8')))).join('\n');
|
||||
for (const expected of [
|
||||
/notifications config[^\n]*R2[^\n]*Job/i,
|
||||
/automation config[^\n]*R2[^\n]*Job/i,
|
||||
/eSIM[^\n]*download[^\n]*R1[^\n]*direct/i,
|
||||
/WLAN connect[^\n]*R1[^\n]*forget[^\n]*R2/i,
|
||||
/DDNS config[^\n]*R2[^\n]*Job/i,
|
||||
/baseband restart[^\n]*R3[^\n]*status[^\n]*R0/i,
|
||||
])
|
||||
assert.match(docs, expected);
|
||||
assert.doesNotMatch(docs, /Notifications[^\n]*config R1/i);
|
||||
assert.doesNotMatch(docs, /Automation[^\n]*config R1/i);
|
||||
});
|
||||
test('R2/R3, dedicated auth, write strategy, and split bulk/fleet policy are gated', () => {
|
||||
for (const row of operationAcceptance58e2204) {
|
||||
const op: any = byId.get(row.operationId);
|
||||
if (['R2', 'R3'].includes(row.riskLevel)) {
|
||||
assert.equal(
|
||||
row.executionMode,
|
||||
op.executionPolicy === 'dedicatedFlow' ? 'dedicated-flow' : 'preparation-job',
|
||||
);
|
||||
assert.match(row.preconditions, /fresh preflight/i);
|
||||
assert.match(row.confirmationUX, /explicit|strong/);
|
||||
assert.match(row.resultDestination, /jobs\/:jobId/);
|
||||
assert.match(row.retryRecovery, /new Job lineage/i);
|
||||
}
|
||||
if (row.riskLevel === 'R3') assert.equal(op.capability, 'job');
|
||||
if (row.method !== 'GET') {
|
||||
assert.notEqual(row.uiStrategy, 'read-panel');
|
||||
assert.notEqual(row.realAcceptance, 'REAL_READ');
|
||||
assert.equal(row.fleetBatchable, false);
|
||||
}
|
||||
}
|
||||
const auth = operationAcceptance58e2204.filter((x) =>
|
||||
[
|
||||
'postAuthSetup',
|
||||
'postAuthPassword',
|
||||
'postAuthSettings',
|
||||
'postAuthLogin',
|
||||
'postAuthLogout',
|
||||
].includes(x.operationId),
|
||||
);
|
||||
assert.equal(auth.length, 5);
|
||||
assert.ok(
|
||||
auth.every(
|
||||
(x) => x.uiStrategy === 'dedicated-auth-flow' && x.executionMode === 'dedicated-flow',
|
||||
),
|
||||
);
|
||||
for (const id of ['postAuthLogin', 'postAuthLogout']) {
|
||||
const row = auth.find((x) => x.operationId === id)!;
|
||||
assert.equal(row.sessionSubtype, 'session-sensitive');
|
||||
assert.match(row.retryRecovery, /no automatic replay/i);
|
||||
assert.match(row.resultDestination, /metadata-only audit/i);
|
||||
}
|
||||
const sms = operationAcceptance58e2204.find((x) => x.operationId === 'postSmsBatchDelete')!;
|
||||
assert.equal(sms.resourceBulk, true);
|
||||
assert.equal(sms.fleetBatchable, false);
|
||||
for (const id of ['postNotificationsQueueRetryAll', 'postNotificationsQueueClear'])
|
||||
assert.equal(
|
||||
operationAcceptance58e2204.find((x) => x.operationId === id)!.resourceBulk,
|
||||
true,
|
||||
id,
|
||||
);
|
||||
for (const id of [
|
||||
'postSmsClear',
|
||||
'postCallHistoryClear',
|
||||
'postNotificationsLogsClear',
|
||||
'postAutomationLogsClear',
|
||||
])
|
||||
assert.equal(
|
||||
operationAcceptance58e2204.find((x) => x.operationId === id)!.resourceBulk,
|
||||
false,
|
||||
id,
|
||||
);
|
||||
const health = operationAcceptance58e2204.find((x) => x.operationId === 'getHealth')!;
|
||||
assert.equal(health.fleetBatchable, true);
|
||||
assert.equal(health.partialAggregationPolicy, 'per-item');
|
||||
});
|
||||
|
||||
test('matrix renders metadata-only safe fixture categories for every operation',()=>{const doc=renderOperationAcceptanceMatrix();const safeCategories=new Set(['success','unsupported','auth-required']);const fixture=new Map(fixtureDisposition58e2204.map(x=>[x.operationId,x]));const rows=[...doc.matchAll(/^\| `([^`]+)` \|.*?\| ([^|]*categories=([^;|]+);[^|]*) \|/gm)].filter(match=>byId.has(match[1]));assert.equal(rows.length,117);for(const [,id,metadata,rendered] of rows){const disposition=fixture.get(id)!;const expected=disposition.observedCategories.length?[...disposition.observedCategories].sort().join(','):'none';assert.equal(rendered.trim(),expected,id);for(const category of disposition.observedCategories)assert.ok(safeCategories.has(category),`${id}:${category}`);assert.doesNotMatch(metadata,/"(?:response|body|sourceInstanceAlias)"\s*:|https?:\/\//i,id);}assert.match(doc,/metadata-only/i);});
|
||||
test('fixture disposition is authoritative and exactly follows collector plus 78-file manifest', () => {
|
||||
assert.equal(fixtureDisposition58e2204.length, 117);
|
||||
assert.equal(new Set(fixtureDisposition58e2204.map((x) => x.operationId)).size, 117);
|
||||
const selected = selectReadonlyOperations(upstream58e2204Operations as any[]);
|
||||
assert.equal(selected.selected.length, 39);
|
||||
assert.equal(selected.denied.length, 11);
|
||||
assert.equal(Object.keys(DENY_REASONS).length, 11);
|
||||
const selectedIds = new Set(selected.selected.map((x: any) => x.operationId));
|
||||
const denied = new Map(selected.denied.map((x: any) => [x.operationId, x.denyReason]));
|
||||
const filesById = new Map<string, any[]>();
|
||||
for (const file of manifest.files) {
|
||||
const match = /instance-[12]--([^.]+)\.json$/.exec(file.path);
|
||||
assert.ok(match);
|
||||
const list = filesById.get(match[1]) ?? [];
|
||||
list.push(file);
|
||||
filesById.set(match[1], list);
|
||||
}
|
||||
for (const d of fixtureDisposition58e2204) {
|
||||
if (selectedIds.has(d.operationId)) {
|
||||
assert.equal(d.runtimeFixtureStatus, 'captured-readonly');
|
||||
assert.equal(d.fixtureCount, 2);
|
||||
assert.equal(filesById.get(d.operationId)?.length, 2);
|
||||
assert.deepEqual(d.aliases, ['instance-1', 'instance-2']);
|
||||
assert.ok(d.observedCategories.length);
|
||||
} else if (denied.has(d.operationId)) {
|
||||
assert.equal(d.runtimeFixtureStatus, 'denied-readonly-candidate');
|
||||
assert.equal(d.reason, denied.get(d.operationId));
|
||||
assert.equal(d.fixtureCount, 0);
|
||||
} else {
|
||||
assert.match(d.runtimeFixtureStatus, /^not-eligible-readonly-capture/);
|
||||
assert.equal(d.fixtureCount, 0);
|
||||
}
|
||||
}
|
||||
assert.equal(manifest.realFixtureCount, 78);
|
||||
});
|
||||
|
||||
test('product risk prose defers to Registry and known conflicts stay corrected', async () => {
|
||||
const productRoot = fileURLToPath(new URL('../../../docs/product/', import.meta.url));
|
||||
const workflows = await readFile(`${productRoot}personas-and-workflows.md`, 'utf8');
|
||||
const charter = await readFile(`${productRoot}project-charter.md`, 'utf8');
|
||||
assert.doesNotMatch(workflows, /下载 profile[^\n]*R2 Job/);
|
||||
assert.match(workflows, /WLAN connect 为 R1/);
|
||||
assert.match(workflows, /notifications config 为 R2 Job/);
|
||||
assert.match(workflows, /fleetBatchable=true/);
|
||||
assert.match(charter, /resourceBulk.*fleetBatchable/);
|
||||
for (const id of [
|
||||
'postEsimProfiles',
|
||||
'postEsimProfilesIccidEnable',
|
||||
'postDeviceNetworkWlanConnect',
|
||||
'postNotificationsConfig',
|
||||
'postAutomationConfig',
|
||||
'postDeviceNetworkDdnsConfig',
|
||||
'postBasebandRestart',
|
||||
]) {
|
||||
assert.ok(
|
||||
operationAcceptance58e2204.find((x) => x.operationId === id),
|
||||
id,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('generated matrix is synchronized and contains each operation exactly once', async () => {
|
||||
const doc = await readFile(matrixPath, 'utf8');
|
||||
assert.equal(doc, renderOperationAcceptanceMatrix());
|
||||
const ids = [...doc.matchAll(/^\| `([^`]+)` \|/gm)].map((x) => x[1]).filter((id) => byId.has(id));
|
||||
assert.equal(ids.length, 117);
|
||||
assert.deepEqual(ids.sort(), [...byId.keys()].sort());
|
||||
});
|
||||
|
||||
test('matrix renders metadata-only safe fixture categories for every operation', () => {
|
||||
const doc = renderOperationAcceptanceMatrix();
|
||||
const safeCategories = new Set(['success', 'unsupported', 'auth-required']);
|
||||
const fixture = new Map(fixtureDisposition58e2204.map((x) => [x.operationId, x]));
|
||||
const rows = [
|
||||
...doc.matchAll(/^\| `([^`]+)` \|.*?\| ([^|]*categories=([^;|]+);[^|]*) \|/gm),
|
||||
].filter((match) => byId.has(match[1]));
|
||||
assert.equal(rows.length, 117);
|
||||
for (const [, id, metadata, rendered] of rows) {
|
||||
const disposition = fixture.get(id)!;
|
||||
const expected = disposition.observedCategories.length
|
||||
? [...disposition.observedCategories].sort().join(',')
|
||||
: 'none';
|
||||
assert.equal(rendered.trim(), expected, id);
|
||||
for (const category of disposition.observedCategories)
|
||||
assert.ok(safeCategories.has(category), `${id}:${category}`);
|
||||
assert.doesNotMatch(metadata, /"(?:response|body|sourceInstanceAlias)"\s*:|https?:\/\//i, id);
|
||||
}
|
||||
assert.match(doc, /metadata-only/i);
|
||||
});
|
||||
|
||||
@@ -4,56 +4,119 @@ import { execFileSync } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { upstream58e2204Operations } from '../src/upstream-58e2204.ts';
|
||||
|
||||
const PHASE_0_2_COMMIT='9dffadbec271227da7ffb192f31dc78c6ee3c2be';
|
||||
const BASELINE_PATH='packages/operation-registry/src/upstream-58e2204.ts';
|
||||
const repoRoot=fileURLToPath(new URL('../../../',import.meta.url));
|
||||
const safetyFields=['operationId','riskLevel','confirmationPolicy','capability','executionPolicy'] as const;
|
||||
type SafetyRow=Record<(typeof safetyFields)[number],string>;
|
||||
const PHASE_0_2_COMMIT = '9dffadbec271227da7ffb192f31dc78c6ee3c2be';
|
||||
const BASELINE_PATH = 'packages/operation-registry/src/upstream-58e2204.ts';
|
||||
const repoRoot = fileURLToPath(new URL('../../../', import.meta.url));
|
||||
const safetyFields = [
|
||||
'operationId',
|
||||
'riskLevel',
|
||||
'confirmationPolicy',
|
||||
'capability',
|
||||
'executionPolicy',
|
||||
] as const;
|
||||
type SafetyRow = Record<(typeof safetyFields)[number], string>;
|
||||
|
||||
function git(...args:string[]){return execFileSync('git',args,{cwd:repoRoot,encoding:'utf8',stdio:['ignore','pipe','pipe']}).trim();}
|
||||
function git(...args: string[]) {
|
||||
return execFileSync('git', args, {
|
||||
cwd: repoRoot,
|
||||
encoding: 'utf8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim();
|
||||
}
|
||||
|
||||
/** Parse only the JSON array literal assigned to the frozen raw operations constant; never evaluate TypeScript. */
|
||||
export function parseFrozenSafetyBaseline(source:string):SafetyRow[]{
|
||||
const marker='const rawUpstream58e2204Operations = ';
|
||||
const start=source.indexOf('[',source.indexOf(marker)+marker.length);
|
||||
assert.ok(source.includes(marker)&&start>=0,'frozen operation array marker missing');
|
||||
let quoted=false,escaped=false,depth=0,end=-1;
|
||||
for(let i=start;i<source.length;i++){
|
||||
const ch=source[i];
|
||||
if(quoted){if(escaped)escaped=false;else if(ch==='\\')escaped=true;else if(ch==='"')quoted=false;continue;}
|
||||
if(ch==='"'){quoted=true;continue;}if(ch==='[')depth++;else if(ch===']'&&--depth===0){end=i+1;break;}
|
||||
}
|
||||
assert.ok(end>start,'unterminated frozen operation array');
|
||||
const parsed=JSON.parse(source.slice(start,end));
|
||||
assert.ok(Array.isArray(parsed),'frozen operation baseline is not an array');
|
||||
return parsed.map((row:any,index:number)=>Object.fromEntries(safetyFields.map(field=>{assert.equal(typeof row?.[field],'string',`baseline row ${index} missing ${field}`);return [field,row[field]];})) as SafetyRow);
|
||||
export function parseFrozenSafetyBaseline(source: string): SafetyRow[] {
|
||||
const marker = 'const rawUpstream58e2204Operations = ';
|
||||
const start = source.indexOf('[', source.indexOf(marker) + marker.length);
|
||||
assert.ok(source.includes(marker) && start >= 0, 'frozen operation array marker missing');
|
||||
let quoted = false,
|
||||
escaped = false,
|
||||
depth = 0,
|
||||
end = -1;
|
||||
for (let i = start; i < source.length; i++) {
|
||||
const ch = source[i];
|
||||
if (quoted) {
|
||||
if (escaped) escaped = false;
|
||||
else if (ch === '\\') escaped = true;
|
||||
else if (ch === '"') quoted = false;
|
||||
continue;
|
||||
}
|
||||
if (ch === '"') {
|
||||
quoted = true;
|
||||
continue;
|
||||
}
|
||||
if (ch === '[') depth++;
|
||||
else if (ch === ']' && --depth === 0) {
|
||||
end = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert.ok(end > start, 'unterminated frozen operation array');
|
||||
const parsed = JSON.parse(source.slice(start, end));
|
||||
assert.ok(Array.isArray(parsed), 'frozen operation baseline is not an array');
|
||||
return parsed.map(
|
||||
(row: any, index: number) =>
|
||||
Object.fromEntries(
|
||||
safetyFields.map((field) => {
|
||||
assert.equal(typeof row?.[field], 'string', `baseline row ${index} missing ${field}`);
|
||||
return [field, row[field]];
|
||||
}),
|
||||
) as SafetyRow,
|
||||
);
|
||||
}
|
||||
|
||||
export function assertExactSafetyBaseline(actual:readonly SafetyRow[],expected:readonly SafetyRow[]){
|
||||
assert.equal(expected.length,117,'independent baseline must contain exactly 117 operations');
|
||||
assert.equal(new Set(expected.map(x=>x.operationId)).size,117,'independent baseline operationIds must be unique');
|
||||
assert.equal(actual.length,117,'current Registry must contain exactly 117 operations');
|
||||
assert.equal(new Set(actual.map(x=>x.operationId)).size,117,'current Registry operationIds must be unique');
|
||||
const sort=(rows:readonly SafetyRow[])=>[...rows].sort((a,b)=>a.operationId.localeCompare(b.operationId));
|
||||
assert.deepEqual(sort(actual),sort(expected),'current Registry safety fields differ from frozen Phase 0.2 Git object; update requires explicit safety review');
|
||||
export function assertExactSafetyBaseline(
|
||||
actual: readonly SafetyRow[],
|
||||
expected: readonly SafetyRow[],
|
||||
) {
|
||||
assert.equal(expected.length, 117, 'independent baseline must contain exactly 117 operations');
|
||||
assert.equal(
|
||||
new Set(expected.map((x) => x.operationId)).size,
|
||||
117,
|
||||
'independent baseline operationIds must be unique',
|
||||
);
|
||||
assert.equal(actual.length, 117, 'current Registry must contain exactly 117 operations');
|
||||
assert.equal(
|
||||
new Set(actual.map((x) => x.operationId)).size,
|
||||
117,
|
||||
'current Registry operationIds must be unique',
|
||||
);
|
||||
const sort = (rows: readonly SafetyRow[]) =>
|
||||
[...rows].sort((a, b) => a.operationId.localeCompare(b.operationId));
|
||||
assert.deepEqual(
|
||||
sort(actual),
|
||||
sort(expected),
|
||||
'current Registry safety fields differ from frozen Phase 0.2 Git object; update requires explicit safety review',
|
||||
);
|
||||
}
|
||||
|
||||
const project=(rows:readonly any[]):SafetyRow[]=>rows.map(row=>Object.fromEntries(safetyFields.map(field=>[field,row[field]])) as SafetyRow);
|
||||
const project = (rows: readonly any[]): SafetyRow[] =>
|
||||
rows.map(
|
||||
(row) => Object.fromEntries(safetyFields.map((field) => [field, row[field]])) as SafetyRow,
|
||||
);
|
||||
|
||||
test('independent Phase 0.2 Git object freezes all 117 Registry safety decisions',()=>{
|
||||
assert.equal(git('rev-parse','9dffadb'),PHASE_0_2_COMMIT);
|
||||
assert.equal(git('cat-file','-t',PHASE_0_2_COMMIT),'commit');
|
||||
const baseline=parseFrozenSafetyBaseline(git('show',`${PHASE_0_2_COMMIT}:${BASELINE_PATH}`));
|
||||
assertExactSafetyBaseline(project(upstream58e2204Operations),baseline);
|
||||
test('independent Phase 0.2 Git object freezes all 117 Registry safety decisions', () => {
|
||||
assert.equal(git('rev-parse', '9dffadb'), PHASE_0_2_COMMIT);
|
||||
assert.equal(git('cat-file', '-t', PHASE_0_2_COMMIT), 'commit');
|
||||
const baseline = parseFrozenSafetyBaseline(git('show', `${PHASE_0_2_COMMIT}:${BASELINE_PATH}`));
|
||||
assertExactSafetyBaseline(project(upstream58e2204Operations), baseline);
|
||||
});
|
||||
|
||||
test('independent safety comparison rejects risk, confirmation, capability and execution-policy mutations',()=>{
|
||||
const baseline=parseFrozenSafetyBaseline(git('show',`${PHASE_0_2_COMMIT}:${BASELINE_PATH}`));
|
||||
const mutations:[string,string,string][]=[
|
||||
['postData','riskLevel','R0'],
|
||||
['postSmsSend','confirmationPolicy','none'],
|
||||
['postSmsSend','capability','query'],
|
||||
['postSmsSend','executionPolicy','dedicatedFlow']
|
||||
];
|
||||
for(const [operationId,field,value] of mutations){const current=structuredClone(project(upstream58e2204Operations));(current.find(x=>x.operationId===operationId)! as any)[field]=value;assert.throws(()=>assertExactSafetyBaseline(current,baseline),/safety fields differ/,`${operationId}:${field}`);}
|
||||
test('independent safety comparison rejects risk, confirmation, capability and execution-policy mutations', () => {
|
||||
const baseline = parseFrozenSafetyBaseline(git('show', `${PHASE_0_2_COMMIT}:${BASELINE_PATH}`));
|
||||
const mutations: [string, string, string][] = [
|
||||
['postData', 'riskLevel', 'R0'],
|
||||
['postSmsSend', 'confirmationPolicy', 'none'],
|
||||
['postSmsSend', 'capability', 'query'],
|
||||
['postSmsSend', 'executionPolicy', 'dedicatedFlow'],
|
||||
];
|
||||
for (const [operationId, field, value] of mutations) {
|
||||
const current = structuredClone(project(upstream58e2204Operations));
|
||||
(current.find((x) => x.operationId === operationId)! as any)[field] = value;
|
||||
assert.throws(
|
||||
() => assertExactSafetyBaseline(current, baseline),
|
||||
/safety fields differ/,
|
||||
`${operationId}:${field}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -10,60 +10,135 @@ const fixturePath = fileURLToPath(new URL('./fixtures/main-routes-58e2204.json',
|
||||
const fixture = JSON.parse(await readFile(fixturePath, 'utf8'));
|
||||
const key = (operation: { method: string; path?: string; pathTemplate?: string }) =>
|
||||
`${operation.method} ${operation.pathTemplate ?? operation.path}`;
|
||||
const evidenceFixturePath = fileURLToPath(new URL('./fixtures/source-evidence-58e2204.json', import.meta.url));
|
||||
const evidenceFixturePath = fileURLToPath(
|
||||
new URL('./fixtures/source-evidence-58e2204.json', import.meta.url),
|
||||
);
|
||||
const evidenceFixture = JSON.parse(await readFile(evidenceFixturePath, 'utf8'));
|
||||
const snapshotRoot = fileURLToPath(new URL('./fixtures/upstream-58e2204/', import.meta.url));
|
||||
const snapshotManifest = JSON.parse(await readFile(`${snapshotRoot}/manifest.json`, 'utf8'));
|
||||
const requiredFields = [
|
||||
'operationId', 'upstreamDomain', 'method', 'pathTemplate', 'handler', 'capability', 'riskLevel',
|
||||
'requestSchemaEvidence', 'responseSchemaEvidence', 'requestContentType', 'timeoutMs',
|
||||
'idempotency', 'confirmationPolicy', 'batchable', 'syncSafe', 'sensitiveFields',
|
||||
'auditedAtCommit', 'compatibilityAdapter', 'uiOwner', 'sourceEvidence', 'responseFixtureStatus',
|
||||
'operationId',
|
||||
'upstreamDomain',
|
||||
'method',
|
||||
'pathTemplate',
|
||||
'handler',
|
||||
'capability',
|
||||
'riskLevel',
|
||||
'requestSchemaEvidence',
|
||||
'responseSchemaEvidence',
|
||||
'requestContentType',
|
||||
'timeoutMs',
|
||||
'idempotency',
|
||||
'confirmationPolicy',
|
||||
'batchable',
|
||||
'syncSafe',
|
||||
'sensitiveFields',
|
||||
'auditedAtCommit',
|
||||
'compatibilityAdapter',
|
||||
'uiOwner',
|
||||
'sourceEvidence',
|
||||
'responseFixtureStatus',
|
||||
] as const;
|
||||
|
||||
const expectedDomains = new Set([
|
||||
'instances-auth', 'device-system', 'sim', 'cellular', 'radio-lock', 'data-connection',
|
||||
'device-network', 'workmode-esim', 'messages', 'calls', 'notifications', 'automation', 'ota',
|
||||
'instances-auth',
|
||||
'device-system',
|
||||
'sim',
|
||||
'cellular',
|
||||
'radio-lock',
|
||||
'data-connection',
|
||||
'device-network',
|
||||
'workmode-esim',
|
||||
'messages',
|
||||
'calls',
|
||||
'notifications',
|
||||
'automation',
|
||||
'ota',
|
||||
]);
|
||||
const expectedOwners = new Set([
|
||||
'instances-new', 'settings-instance', 'fleet', 'overview', 'cellular', 'device-network',
|
||||
'messages', 'calls', 'esim', 'notifications', 'automation', 'ota',
|
||||
'instances-new',
|
||||
'settings-instance',
|
||||
'fleet',
|
||||
'overview',
|
||||
'cellular',
|
||||
'device-network',
|
||||
'messages',
|
||||
'calls',
|
||||
'esim',
|
||||
'notifications',
|
||||
'automation',
|
||||
'ota',
|
||||
]);
|
||||
const operationKey = (operation: { method: string; pathTemplate: string; handler: string }) =>
|
||||
`${operation.method} ${operation.pathTemplate} ${operation.handler}`;
|
||||
|
||||
test('sensitive fields are independently snapshot-rebuildable model-chain anchors', async () => {
|
||||
const directionClaim = (operation: any, direction: string) =>
|
||||
['request', 'path', 'query'].includes(direction) ? operation.requestEvidence : operation.responseEvidence;
|
||||
for (const operation of upstream58e2204Operations as any[]) for (const field of operation.sensitiveFields) {
|
||||
const evidence = field.evidence;
|
||||
assert.ok(evidence, `${operation.operationId}: ${field.path} missing evidence`);
|
||||
assert.match(evidence.sourceSha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(evidence.sourceFile && evidence.symbol && (evidence.fieldPath || evidence.modelField || evidence.dynamicKey));
|
||||
const source = await readFile(`${snapshotRoot}/${evidence.sourceFile}`, 'utf8');
|
||||
const slice = source.split('\n').slice(evidence.startLine - 1, evidence.endLine).join('\n');
|
||||
assert.equal(createHash('sha256').update(slice).digest('hex'), evidence.sourceSha256, `${operation.operationId}: ${field.path}`);
|
||||
assert.ok(directionClaim(operation, field.direction), `${operation.operationId}: direction`);
|
||||
if (evidence.handlerToken) assert.ok(slice.includes(evidence.handlerToken));
|
||||
else if (evidence.dynamicKey) assert.match(slice, new RegExp(`["']${evidence.dynamicKey}["']`));
|
||||
else {
|
||||
assert.match(slice, new RegExp(`(?:struct|enum)\\s+${evidence.symbol}\\b`));
|
||||
assert.match(slice, new RegExp(`\\b${evidence.modelField}\\s*:`));
|
||||
assert.equal(evidence.modelField, field.path.replace(/\[\*\]/g, '').split('.').at(-1));
|
||||
for (const link of evidence.containerPath ?? []) {
|
||||
const linkSource = await readFile(`${snapshotRoot}/${link.sourceFile}`, 'utf8');
|
||||
const linkSlice = linkSource.split('\n').slice(link.startLine - 1, link.endLine).join('\n');
|
||||
assert.equal(createHash('sha256').update(linkSlice).digest('hex'), link.sourceSha256);
|
||||
assert.match(linkSlice, new RegExp(`struct\\s+${link.symbol}\\b`));
|
||||
assert.match(linkSlice, new RegExp(`\\b${link.field}\\s*:\\s*(?:Vec<)?${link.targetSymbol}`));
|
||||
['request', 'path', 'query'].includes(direction)
|
||||
? operation.requestEvidence
|
||||
: operation.responseEvidence;
|
||||
for (const operation of upstream58e2204Operations as any[])
|
||||
for (const field of operation.sensitiveFields) {
|
||||
const evidence = field.evidence;
|
||||
assert.ok(evidence, `${operation.operationId}: ${field.path} missing evidence`);
|
||||
assert.match(evidence.sourceSha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(
|
||||
evidence.sourceFile &&
|
||||
evidence.symbol &&
|
||||
(evidence.fieldPath || evidence.modelField || evidence.dynamicKey),
|
||||
);
|
||||
const source = await readFile(`${snapshotRoot}/${evidence.sourceFile}`, 'utf8');
|
||||
const slice = source
|
||||
.split('\n')
|
||||
.slice(evidence.startLine - 1, evidence.endLine)
|
||||
.join('\n');
|
||||
assert.equal(
|
||||
createHash('sha256').update(slice).digest('hex'),
|
||||
evidence.sourceSha256,
|
||||
`${operation.operationId}: ${field.path}`,
|
||||
);
|
||||
assert.ok(directionClaim(operation, field.direction), `${operation.operationId}: direction`);
|
||||
if (evidence.handlerToken) assert.ok(slice.includes(evidence.handlerToken));
|
||||
else if (evidence.dynamicKey)
|
||||
assert.match(slice, new RegExp(`["']${evidence.dynamicKey}["']`));
|
||||
else {
|
||||
assert.match(slice, new RegExp(`(?:struct|enum)\\s+${evidence.symbol}\\b`));
|
||||
assert.match(slice, new RegExp(`\\b${evidence.modelField}\\s*:`));
|
||||
assert.equal(
|
||||
evidence.modelField,
|
||||
field.path
|
||||
.replace(/\[\*\]/g, '')
|
||||
.split('.')
|
||||
.at(-1),
|
||||
);
|
||||
for (const link of evidence.containerPath ?? []) {
|
||||
const linkSource = await readFile(`${snapshotRoot}/${link.sourceFile}`, 'utf8');
|
||||
const linkSlice = linkSource
|
||||
.split('\n')
|
||||
.slice(link.startLine - 1, link.endLine)
|
||||
.join('\n');
|
||||
assert.equal(createHash('sha256').update(linkSlice).digest('hex'), link.sourceSha256);
|
||||
assert.match(linkSlice, new RegExp(`struct\\s+${link.symbol}\\b`));
|
||||
assert.match(
|
||||
linkSlice,
|
||||
new RegExp(`\\b${link.field}\\s*:\\s*(?:Vec<)?${link.targetSymbol}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const get = (method: string, path: string) => (upstream58e2204Operations as any[]).find(o => o.method === method && o.pathTemplate === path);
|
||||
assert.ok(!get('GET','/api/sim').sensitiveFields.some((f: any) => f.path.endsWith('.imei')));
|
||||
assert.deepEqual(get('GET','/api/device-network/wlan/status').sensitiveFields.map((f: any) => f.path), [
|
||||
'$.response.data.ssid', '$.response.data.ipv4_addresses', '$.response.data.ipv6_addresses']);
|
||||
assert.deepEqual(get('GET','/api/device-network/wlan/profiles').sensitiveFields.map((f: any) => f.path), ['$.response.data.profiles[*].ssid']);
|
||||
const get = (method: string, path: string) =>
|
||||
(upstream58e2204Operations as any[]).find(
|
||||
(o) => o.method === method && o.pathTemplate === path,
|
||||
);
|
||||
assert.ok(!get('GET', '/api/sim').sensitiveFields.some((f: any) => f.path.endsWith('.imei')));
|
||||
assert.deepEqual(
|
||||
get('GET', '/api/device-network/wlan/status').sensitiveFields.map((f: any) => f.path),
|
||||
['$.response.data.ssid', '$.response.data.ipv4_addresses', '$.response.data.ipv6_addresses'],
|
||||
);
|
||||
assert.deepEqual(
|
||||
get('GET', '/api/device-network/wlan/profiles').sensitiveFields.map((f: any) => f.path),
|
||||
['$.response.data.profiles[*].ssid'],
|
||||
);
|
||||
});
|
||||
|
||||
test('Phase 0.2 uses structured, snapshot-rebuildable evidence and directional redaction contracts', () => {
|
||||
@@ -71,9 +146,14 @@ test('Phase 0.2 uses structured, snapshot-rebuildable evidence and directional r
|
||||
for (const name of ['requestEvidence', 'responseEvidence']) {
|
||||
const claim = operation[name];
|
||||
assert.ok(claim && typeof claim === 'object', `${operation.operationId}: ${name}`);
|
||||
assert.match(claim.kind, /^(none|path|query|json|multipart|bytes|explicit-return|api-response|dynamic-json|opaque)$/);
|
||||
assert.match(
|
||||
claim.kind,
|
||||
/^(none|path|query|json|multipart|bytes|explicit-return|api-response|dynamic-json|opaque)$/,
|
||||
);
|
||||
assert.match(claim.sourceSha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(claim.sourceFile && Number.isInteger(claim.startLine) && Number.isInteger(claim.endLine));
|
||||
assert.ok(
|
||||
claim.sourceFile && Number.isInteger(claim.startLine) && Number.isInteger(claim.endLine),
|
||||
);
|
||||
assert.ok(Array.isArray(claim.symbols));
|
||||
assert.ok(Array.isArray(claim.modelEvidence));
|
||||
}
|
||||
@@ -85,56 +165,148 @@ test('Phase 0.2 uses structured, snapshot-rebuildable evidence and directional r
|
||||
assert.ok(field.sourceAnchor?.symbol || field.sourceAnchor?.token);
|
||||
}
|
||||
}
|
||||
const get = (method: string, path: string) => (upstream58e2204Operations as any[]).find(o => o.method === method && o.pathTemplate === path);
|
||||
assert.ok(get('GET', '/api/sms/list').responseEvidence.symbols.some((s: any) => s.symbol === 'SmsListResponse'));
|
||||
assert.ok(get('GET', '/api/device').responseEvidence.symbols.some((s: any) => s.symbol === 'DeviceInfoResponse'));
|
||||
assert.ok(get('GET', '/api/network').responseEvidence.symbols.some((s: any) => s.symbol === 'NetworkInfoResponse'));
|
||||
assert.ok(get('POST', '/api/ota/upload').responseEvidence.symbols.some((s: any) => s.symbol === 'OtaUploadResponse'));
|
||||
const get = (method: string, path: string) =>
|
||||
(upstream58e2204Operations as any[]).find(
|
||||
(o) => o.method === method && o.pathTemplate === path,
|
||||
);
|
||||
assert.ok(
|
||||
get('GET', '/api/sms/list').responseEvidence.symbols.some(
|
||||
(s: any) => s.symbol === 'SmsListResponse',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
get('GET', '/api/device').responseEvidence.symbols.some(
|
||||
(s: any) => s.symbol === 'DeviceInfoResponse',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
get('GET', '/api/network').responseEvidence.symbols.some(
|
||||
(s: any) => s.symbol === 'NetworkInfoResponse',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
get('POST', '/api/ota/upload').responseEvidence.symbols.some(
|
||||
(s: any) => s.symbol === 'OtaUploadResponse',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('structured claims are rebuilt from the independent snapshot, with exact braces and symbols', async () => {
|
||||
for (const operation of upstream58e2204Operations as any[]) for (const claim of [operation.requestEvidence, operation.responseEvidence]) {
|
||||
const source = await readFile(`${snapshotRoot}/${claim.sourceFile}`, 'utf8');
|
||||
const slice = source.split('\n').slice(claim.startLine - 1, claim.endLine).join('\n');
|
||||
assert.equal(createHash('sha256').update(slice).digest('hex'), claim.sourceSha256, operation.operationId);
|
||||
const fn = operation.handler.split('::').at(-1); const signature = new RegExp(`(?:pub\\s+)?async\\s+fn\\s+${fn}\\b`).exec(source);
|
||||
assert.ok(signature); const open = source.indexOf('{', signature!.index); let depth = 0, close = -1;
|
||||
for (let i = open; i < source.length; i++) { if (source[i] === '{') depth++; else if (source[i] === '}' && --depth === 0) { close = i; break; } }
|
||||
assert.equal(source.slice(0, close + 1).split('\n').length, claim.endLine, `${operation.operationId}: exact end`);
|
||||
for (const symbol of claim.symbols) assert.ok(slice.includes(symbol.symbol) || claim.modelEvidence.some((m: any) => m.symbol === symbol.symbol), `${operation.operationId}: ${symbol.symbol}`);
|
||||
for (const model of claim.modelEvidence) {
|
||||
const modelSource = await readFile(`${snapshotRoot}/${model.sourceFile}`, 'utf8');
|
||||
const modelSlice = modelSource.split('\n').slice(model.startLine - 1, model.endLine).join('\n');
|
||||
assert.equal(createHash('sha256').update(modelSlice).digest('hex'), model.sourceSha256, model.symbol);
|
||||
assert.match(modelSlice, new RegExp(`(?:struct|enum)\\s+${model.symbol}\\b`));
|
||||
for (const field of model.fields) assert.match(modelSlice, new RegExp(`\\b${field}\\s*:`));
|
||||
for (const operation of upstream58e2204Operations as any[])
|
||||
for (const claim of [operation.requestEvidence, operation.responseEvidence]) {
|
||||
const source = await readFile(`${snapshotRoot}/${claim.sourceFile}`, 'utf8');
|
||||
const slice = source
|
||||
.split('\n')
|
||||
.slice(claim.startLine - 1, claim.endLine)
|
||||
.join('\n');
|
||||
assert.equal(
|
||||
createHash('sha256').update(slice).digest('hex'),
|
||||
claim.sourceSha256,
|
||||
operation.operationId,
|
||||
);
|
||||
const fn = operation.handler.split('::').at(-1);
|
||||
const signature = new RegExp(`(?:pub\\s+)?async\\s+fn\\s+${fn}\\b`).exec(source);
|
||||
assert.ok(signature);
|
||||
const open = source.indexOf('{', signature!.index);
|
||||
let depth = 0,
|
||||
close = -1;
|
||||
for (let i = open; i < source.length; i++) {
|
||||
if (source[i] === '{') depth++;
|
||||
else if (source[i] === '}' && --depth === 0) {
|
||||
close = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert.equal(
|
||||
source.slice(0, close + 1).split('\n').length,
|
||||
claim.endLine,
|
||||
`${operation.operationId}: exact end`,
|
||||
);
|
||||
for (const symbol of claim.symbols)
|
||||
assert.ok(
|
||||
slice.includes(symbol.symbol) ||
|
||||
claim.modelEvidence.some((m: any) => m.symbol === symbol.symbol),
|
||||
`${operation.operationId}: ${symbol.symbol}`,
|
||||
);
|
||||
for (const model of claim.modelEvidence) {
|
||||
const modelSource = await readFile(`${snapshotRoot}/${model.sourceFile}`, 'utf8');
|
||||
const modelSlice = modelSource
|
||||
.split('\n')
|
||||
.slice(model.startLine - 1, model.endLine)
|
||||
.join('\n');
|
||||
assert.equal(
|
||||
createHash('sha256').update(modelSlice).digest('hex'),
|
||||
model.sourceSha256,
|
||||
model.symbol,
|
||||
);
|
||||
assert.match(modelSlice, new RegExp(`(?:struct|enum)\\s+${model.symbol}\\b`));
|
||||
for (const field of model.fields) assert.match(modelSlice, new RegExp(`\\b${field}\\s*:`));
|
||||
}
|
||||
if (claim.kind === 'dynamic-json') assert.match(slice, /json!|Json\s*\(|ApiResponse::/);
|
||||
}
|
||||
if (claim.kind === 'dynamic-json') assert.match(slice, /json!|Json\s*\(|ApiResponse::/);
|
||||
}
|
||||
});
|
||||
|
||||
test('Axum route chains are independently parsed from frozen main.rs', async () => {
|
||||
const source = await readFile(`${snapshotRoot}/backend/src/main.rs`, 'utf8'); const parsed: string[] = [];
|
||||
for (let from = 0; ;) {
|
||||
const start = source.indexOf('.route(', from); if (start < 0) break; let depth = 0, quote = false, end = -1;
|
||||
for (let i = start + 6; i < source.length; i++) { const c = source[i]; if (c === '"' && source[i - 1] !== '\\') quote = !quote; if (!quote && c === '(') depth++; else if (!quote && c === ')' && --depth === 0) { end = i; break; } }
|
||||
const chain = source.slice(start, end + 1); from = end + 1; const path = /\.route\(\s*"([^"]+)"/.exec(chain)?.[1]; if (!path) continue;
|
||||
for (const match of chain.matchAll(/\.(get|post|delete)\(([\w:]+)\)|\b(get|post|delete)\(([\w:]+)\)/g)) { const method = (match[1] ?? match[3]).toUpperCase(); const handler = match[2] ?? match[4]; if (handler !== 'options_handler') parsed.push(`${method} ${path} ${handler}`); }
|
||||
const source = await readFile(`${snapshotRoot}/backend/src/main.rs`, 'utf8');
|
||||
const parsed: string[] = [];
|
||||
for (let from = 0; ; ) {
|
||||
const start = source.indexOf('.route(', from);
|
||||
if (start < 0) break;
|
||||
let depth = 0,
|
||||
quote = false,
|
||||
end = -1;
|
||||
for (let i = start + 6; i < source.length; i++) {
|
||||
const c = source[i];
|
||||
if (c === '"' && source[i - 1] !== '\\') quote = !quote;
|
||||
if (!quote && c === '(') depth++;
|
||||
else if (!quote && c === ')' && --depth === 0) {
|
||||
end = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const chain = source.slice(start, end + 1);
|
||||
from = end + 1;
|
||||
const path = /\.route\(\s*"([^"]+)"/.exec(chain)?.[1];
|
||||
if (!path) continue;
|
||||
for (const match of chain.matchAll(
|
||||
/\.(get|post|delete)\(([\w:]+)\)|\b(get|post|delete)\(([\w:]+)\)/g,
|
||||
)) {
|
||||
const method = (match[1] ?? match[3]).toUpperCase();
|
||||
const handler = match[2] ?? match[4];
|
||||
if (handler !== 'options_handler') parsed.push(`${method} ${path} ${handler}`);
|
||||
}
|
||||
}
|
||||
const expected = fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort();
|
||||
assert.deepEqual(parsed.sort(), expected); assert.deepEqual(parsed.sort(), (upstream58e2204Operations as any[]).map(operationKey).sort());
|
||||
assert.deepEqual(parsed.sort(), expected);
|
||||
assert.deepEqual(parsed.sort(), (upstream58e2204Operations as any[]).map(operationKey).sort());
|
||||
});
|
||||
|
||||
test('Bruno files are independently parsed and agree with structured request claims', async () => {
|
||||
for (const evidence of evidenceFixture.operations) for (const bruno of evidence.brunoEvidence ?? []) {
|
||||
const text = await readFile(`${snapshotRoot}/${bruno.file}`, 'utf8'); const method = /\b(get|post|delete)\s*\{/.exec(text)?.[1].toUpperCase();
|
||||
const url = /url:\s*(?:\{\{baseUrl\}\}|https?:\/\/[^/\s]+)(\/[^\s]+)/.exec(text)?.[1]; assert.equal(method, bruno.method); assert.equal(url, bruno.path);
|
||||
const bodyMode = /body:(json|multipart|text)/.exec(text)?.[1] ?? 'none'; assert.equal(bodyMode, bruno.bodyMode, bruno.file);
|
||||
const operation: any = (upstream58e2204Operations as any[]).find(o => o.method === method && (o.pathTemplate === url || new RegExp(`^${o.pathTemplate.replace(/\{[^}]+\}/g, '[^/]+')}$`).test(url!))); assert.ok(operation, bruno.file);
|
||||
if (bodyMode === 'json' && bruno.bodyFields.length) assert.equal(operation.requestEvidence.kind, 'json', bruno.file);
|
||||
if (bodyMode === 'json' && !bruno.bodyFields.length) assert.ok(['none','path','query','json'].includes(operation.requestEvidence.kind), `${bruno.file}: empty Bruno JSON has no contract fields`);
|
||||
for (const field of bruno.bodyFields) assert.match(text, new RegExp(`\\b${field}\\b`));
|
||||
}
|
||||
for (const evidence of evidenceFixture.operations)
|
||||
for (const bruno of evidence.brunoEvidence ?? []) {
|
||||
const text = await readFile(`${snapshotRoot}/${bruno.file}`, 'utf8');
|
||||
const method = /\b(get|post|delete)\s*\{/.exec(text)?.[1].toUpperCase();
|
||||
const url = /url:\s*(?:\{\{baseUrl\}\}|https?:\/\/[^/\s]+)(\/[^\s]+)/.exec(text)?.[1];
|
||||
assert.equal(method, bruno.method);
|
||||
assert.equal(url, bruno.path);
|
||||
const bodyMode = /body:(json|multipart|text)/.exec(text)?.[1] ?? 'none';
|
||||
assert.equal(bodyMode, bruno.bodyMode, bruno.file);
|
||||
const operation: any = (upstream58e2204Operations as any[]).find(
|
||||
(o) =>
|
||||
o.method === method &&
|
||||
(o.pathTemplate === url ||
|
||||
new RegExp(`^${o.pathTemplate.replace(/\{[^}]+\}/g, '[^/]+')}$`).test(url!)),
|
||||
);
|
||||
assert.ok(operation, bruno.file);
|
||||
if (bodyMode === 'json' && bruno.bodyFields.length)
|
||||
assert.equal(operation.requestEvidence.kind, 'json', bruno.file);
|
||||
if (bodyMode === 'json' && !bruno.bodyFields.length)
|
||||
assert.ok(
|
||||
['none', 'path', 'query', 'json'].includes(operation.requestEvidence.kind),
|
||||
`${bruno.file}: empty Bruno JSON has no contract fields`,
|
||||
);
|
||||
for (const field of bruno.bodyFields) assert.match(text, new RegExp(`\\b${field}\\b`));
|
||||
}
|
||||
});
|
||||
|
||||
test('frozen fixture has the audited upstream route totals', () => {
|
||||
@@ -143,31 +315,54 @@ test('frozen fixture has the audited upstream route totals', () => {
|
||||
assert.equal(new Set(fixture.routes.map(key)).size, 117);
|
||||
assert.equal(new Set(fixture.routes.map((route: { path: string }) => route.path)).size, 100);
|
||||
assert.deepEqual(
|
||||
Object.fromEntries(['GET', 'POST', 'DELETE'].map(method => [method, fixture.routes.filter((r: { method: string }) => r.method === method).length])),
|
||||
Object.fromEntries(
|
||||
['GET', 'POST', 'DELETE'].map((method) => [
|
||||
method,
|
||||
fixture.routes.filter((r: { method: string }) => r.method === method).length,
|
||||
]),
|
||||
),
|
||||
{ GET: 50, POST: 62, DELETE: 5 },
|
||||
);
|
||||
});
|
||||
|
||||
test('registry is exactly route-parity complete with independent fixture', () => {
|
||||
assert.equal(upstream58e2204Operations.length, 117);
|
||||
const fixtureTriples = fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort();
|
||||
const fixtureTriples = fixture.routes
|
||||
.map((r: any) => `${r.method} ${r.path} ${r.handler}`)
|
||||
.sort();
|
||||
assert.deepEqual(upstream58e2204Operations.map(operationKey).sort(), fixtureTriples);
|
||||
assert.equal(new Set(upstream58e2204Operations.map(operation => operation.pathTemplate)).size, 100);
|
||||
assert.equal(new Set(upstream58e2204Operations.map(operation => operation.operationId)).size, 117);
|
||||
assert.equal(
|
||||
new Set(upstream58e2204Operations.map((operation) => operation.pathTemplate)).size,
|
||||
100,
|
||||
);
|
||||
assert.equal(
|
||||
new Set(upstream58e2204Operations.map((operation) => operation.operationId)).size,
|
||||
117,
|
||||
);
|
||||
for (const operation of upstream58e2204Operations) {
|
||||
const route = fixture.routes.find((r: any) => `${r.method} ${r.path} ${r.handler}` === operationKey(operation));
|
||||
const route = fixture.routes.find(
|
||||
(r: any) => `${r.method} ${r.path} ${r.handler}` === operationKey(operation),
|
||||
);
|
||||
assert.ok(route, operationKey(operation));
|
||||
assert.ok(operation.sourceEvidence.includes(route.source), `${operation.operationId}: exact main.rs evidence`);
|
||||
assert.ok(
|
||||
operation.sourceEvidence.includes(route.source),
|
||||
`${operation.operationId}: exact main.rs evidence`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('frozen static-contract evidence is complete and bound to routes', () => {
|
||||
assert.equal(evidenceFixture.upstreamCommit, fixture.upstreamCommit);
|
||||
assert.equal(evidenceFixture.operations.length, 117);
|
||||
assert.deepEqual(evidenceFixture.operations.map((e: any) => `${e.method} ${e.path} ${e.handler}`).sort(),
|
||||
fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort());
|
||||
assert.deepEqual(
|
||||
evidenceFixture.operations.map((e: any) => `${e.method} ${e.path} ${e.handler}`).sort(),
|
||||
fixture.routes.map((r: any) => `${r.method} ${r.path} ${r.handler}`).sort(),
|
||||
);
|
||||
for (const evidence of evidenceFixture.operations) {
|
||||
assert.match(evidence.handlerEvidence, /^backend\/src\/(?:handlers|auth|notification_queue)\.rs:\d+-\d+$/);
|
||||
assert.match(
|
||||
evidence.handlerEvidence,
|
||||
/^backend\/src\/(?:handlers|auth|notification_queue)\.rs:\d+-\d+$/,
|
||||
);
|
||||
assert.match(evidence.sha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(evidence.requestSummary && evidence.responseSummary);
|
||||
}
|
||||
@@ -182,26 +377,63 @@ test('source evidence is independently reproducible from frozen whole-file snaps
|
||||
}
|
||||
for (const evidence of evidenceFixture.operations) {
|
||||
const range = evidence.handlerRange;
|
||||
assert.ok(range && Number.isInteger(range.startLine) && Number.isInteger(range.endLine), evidence.handler);
|
||||
assert.ok(
|
||||
range && Number.isInteger(range.startLine) && Number.isInteger(range.endLine),
|
||||
evidence.handler,
|
||||
);
|
||||
const source = await readFile(`${snapshotRoot}/${range.file}`, 'utf8');
|
||||
const slice = source.split('\n').slice(range.startLine - 1, range.endLine).join('\n');
|
||||
assert.equal(createHash('sha256').update(slice).digest('hex'), evidence.sha256, evidence.handler);
|
||||
const slice = source
|
||||
.split('\n')
|
||||
.slice(range.startLine - 1, range.endLine)
|
||||
.join('\n');
|
||||
assert.equal(
|
||||
createHash('sha256').update(slice).digest('hex'),
|
||||
evidence.sha256,
|
||||
evidence.handler,
|
||||
);
|
||||
assert.match(slice, new RegExp(`async\\s+fn\\s+${evidence.handler.split('::').at(-1)}\\b`));
|
||||
assert.equal((slice.match(/{/g) ?? []).length, (slice.match(/}/g) ?? []).length, evidence.handler);
|
||||
assert.ok(evidence.extractorTokens.every((token: string) => slice.replace(/\\s+/g, '').includes(token.replace(/\\s+/g, ''))), evidence.handler);
|
||||
assert.ok(evidence.responseKind && evidence.responseTypeEvidence !== 'Rust return impl IntoResponse', evidence.handler);
|
||||
assert.equal(
|
||||
(slice.match(/{/g) ?? []).length,
|
||||
(slice.match(/}/g) ?? []).length,
|
||||
evidence.handler,
|
||||
);
|
||||
assert.ok(
|
||||
evidence.extractorTokens.every((token: string) =>
|
||||
slice.replace(/\\s+/g, '').includes(token.replace(/\\s+/g, '')),
|
||||
),
|
||||
evidence.handler,
|
||||
);
|
||||
assert.ok(
|
||||
evidence.responseKind && evidence.responseTypeEvidence !== 'Rust return impl IntoResponse',
|
||||
evidence.handler,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('high-value response, confirmation, sensitive-field and Bruno contracts are fixed', () => {
|
||||
const operations: any[] = upstream58e2204Operations as any;
|
||||
const byPath = (method: string, path: string) => operations.find(o => o.method === method && o.pathTemplate === path)!;
|
||||
assert.match(byPath('GET', '/api/device').responseSchemaEvidence, /ApiResponse<DeviceInfoResponse>/);
|
||||
assert.match(byPath('GET', '/api/network').responseSchemaEvidence, /ApiResponse<NetworkInfoResponse>/);
|
||||
assert.match(byPath('POST', '/api/ota/upload').responseSchemaEvidence, /ApiResponse<OtaUploadResponse>/);
|
||||
const byPath = (method: string, path: string) =>
|
||||
operations.find((o) => o.method === method && o.pathTemplate === path)!;
|
||||
assert.match(
|
||||
byPath('GET', '/api/device').responseSchemaEvidence,
|
||||
/ApiResponse<DeviceInfoResponse>/,
|
||||
);
|
||||
assert.match(
|
||||
byPath('GET', '/api/network').responseSchemaEvidence,
|
||||
/ApiResponse<NetworkInfoResponse>/,
|
||||
);
|
||||
assert.match(
|
||||
byPath('POST', '/api/ota/upload').responseSchemaEvidence,
|
||||
/ApiResponse<OtaUploadResponse>/,
|
||||
);
|
||||
for (const operation of operations) {
|
||||
if (operation.riskLevel === 'R2') assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy), operation.operationId);
|
||||
if (operation.riskLevel === 'R3') assert.equal(operation.confirmationPolicy, 'strong', operation.operationId);
|
||||
if (operation.riskLevel === 'R2')
|
||||
assert.ok(
|
||||
['explicit', 'strong'].includes(operation.confirmationPolicy),
|
||||
operation.operationId,
|
||||
);
|
||||
if (operation.riskLevel === 'R3')
|
||||
assert.equal(operation.confirmationPolicy, 'strong', operation.operationId);
|
||||
}
|
||||
const expectedSensitive: Record<string, string[]> = {
|
||||
'POST /api/esim/profiles': ['$.body.matching_id', '$.body.confirmation_code', '$.body.imei'],
|
||||
@@ -215,7 +447,11 @@ test('high-value response, confirmation, sensitive-field and Bruno contracts are
|
||||
};
|
||||
for (const [route, fields] of Object.entries(expectedSensitive)) {
|
||||
const [method, path] = route.split(' ');
|
||||
for (const field of fields) assert.ok(byPath(method, path).sensitiveFields.some((claim: any) => claim.path === field), `${route}: ${field}`);
|
||||
for (const field of fields)
|
||||
assert.ok(
|
||||
byPath(method, path).sensitiveFields.some((claim: any) => claim.path === field),
|
||||
`${route}: ${field}`,
|
||||
);
|
||||
}
|
||||
for (const evidence of evidenceFixture.operations) {
|
||||
assert.match(evidence.evidenceLevel, /^route\+handler(?:\+model)?(?:\+bruno)?$/);
|
||||
@@ -225,20 +461,41 @@ test('high-value response, confirmation, sensitive-field and Bruno contracts are
|
||||
|
||||
test('every operation carries auditable orchestration metadata', () => {
|
||||
for (const operation of upstream58e2204Operations) {
|
||||
for (const field of requiredFields) assert.ok(field in operation, `${operation.operationId}: missing ${field}`);
|
||||
assert.ok(expectedDomains.has(operation.upstreamDomain), `${operation.operationId}: unknown domain`);
|
||||
for (const field of requiredFields)
|
||||
assert.ok(field in operation, `${operation.operationId}: missing ${field}`);
|
||||
assert.ok(
|
||||
expectedDomains.has(operation.upstreamDomain),
|
||||
`${operation.operationId}: unknown domain`,
|
||||
);
|
||||
assert.ok(expectedOwners.has(operation.uiOwner), `${operation.operationId}: unknown owner`);
|
||||
assert.match(operation.operationId, /^[a-z][A-Za-z0-9]*$/);
|
||||
assert.ok(['R0', 'R1', 'R2', 'R3'].includes(operation.riskLevel));
|
||||
assert.ok(Number.isInteger(operation.timeoutMs) && operation.timeoutMs > 0);
|
||||
assert.ok(Array.isArray(operation.sensitiveFields));
|
||||
assert.ok(Array.isArray(operation.sourceEvidence) && operation.sourceEvidence.length > 0);
|
||||
assert.ok(operation.sourceEvidence.some(evidence => /^backend\/src\/main\.rs:\d+-\d+$/.test(evidence)));
|
||||
assert.ok(operation.handler && operation.requestSchemaEvidence && operation.responseSchemaEvidence);
|
||||
for (const field of ['requestSchemaEvidence', 'responseSchemaEvidence', 'requestContentType', 'idempotency'] as const) {
|
||||
assert.doesNotMatch(operation[field], /TODO_EVIDENCE|not frozen/i, `${operation.operationId}: ${field}`);
|
||||
assert.ok(
|
||||
operation.sourceEvidence.some((evidence) =>
|
||||
/^backend\/src\/main\.rs:\d+-\d+$/.test(evidence),
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
operation.handler && operation.requestSchemaEvidence && operation.responseSchemaEvidence,
|
||||
);
|
||||
for (const field of [
|
||||
'requestSchemaEvidence',
|
||||
'responseSchemaEvidence',
|
||||
'requestContentType',
|
||||
'idempotency',
|
||||
] as const) {
|
||||
assert.doesNotMatch(
|
||||
operation[field],
|
||||
/TODO_EVIDENCE|not frozen/i,
|
||||
`${operation.operationId}: ${field}`,
|
||||
);
|
||||
}
|
||||
assert.ok(['safe', 'idempotent', 'non-idempotent', 'conditional'].includes(operation.idempotency));
|
||||
assert.ok(
|
||||
['safe', 'idempotent', 'non-idempotent', 'conditional'].includes(operation.idempotency),
|
||||
);
|
||||
assert.equal(operation.auditedAtCommit, fixture.upstreamCommit);
|
||||
assert.equal(operation.compatibilityAdapter, 'none-pinned-commit');
|
||||
if (operation.riskLevel === 'R2') assert.equal(operation.syncSafe, false);
|
||||
@@ -247,34 +504,49 @@ test('every operation carries auditable orchestration metadata', () => {
|
||||
assert.equal(operation.syncSafe, false);
|
||||
assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy));
|
||||
}
|
||||
if (operation.batchable && operation.riskLevel === 'R2') assert.equal(operation.capability, 'job');
|
||||
if (operation.batchable && operation.riskLevel === 'R2')
|
||||
assert.equal(operation.capability, 'job');
|
||||
}
|
||||
});
|
||||
|
||||
test('dangerous authentication endpoints use dedicated flows, never generic proxy', () => {
|
||||
const dangerous = upstream58e2204Operations.filter(operation =>
|
||||
['/api/auth/setup', '/api/auth/password'].includes(operation.pathTemplate));
|
||||
const dangerous = upstream58e2204Operations.filter((operation) =>
|
||||
['/api/auth/setup', '/api/auth/password'].includes(operation.pathTemplate),
|
||||
);
|
||||
assert.equal(dangerous.length, 2);
|
||||
for (const operation of dangerous) {
|
||||
assert.equal(operation.riskLevel, 'R3');
|
||||
assert.equal(operation.executionPolicy, 'dedicatedFlow');
|
||||
}
|
||||
const loginLogout = upstream58e2204Operations.filter(operation =>
|
||||
['/api/auth/login', '/api/auth/logout'].includes(operation.pathTemplate));
|
||||
const loginLogout = upstream58e2204Operations.filter((operation) =>
|
||||
['/api/auth/login', '/api/auth/logout'].includes(operation.pathTemplate),
|
||||
);
|
||||
assert.equal(loginLogout.length, 2);
|
||||
assert.ok(loginLogout.every(operation => operation.riskLevel === 'R1' && operation.sessionSensitive));
|
||||
const authSettingsPost = upstream58e2204Operations.find(operation => key(operation) === 'POST /api/auth/settings');
|
||||
assert.ok(
|
||||
loginLogout.every((operation) => operation.riskLevel === 'R1' && operation.sessionSensitive),
|
||||
);
|
||||
const authSettingsPost = upstream58e2204Operations.find(
|
||||
(operation) => key(operation) === 'POST /api/auth/settings',
|
||||
);
|
||||
assert.ok(authSettingsPost && ['R2', 'R3'].includes(authSettingsPost.riskLevel));
|
||||
assert.equal(authSettingsPost?.syncSafe, false);
|
||||
});
|
||||
|
||||
test('product risk floors and auth replay policy are enforced', () => {
|
||||
const byPath = (method: string, path: string) => upstream58e2204Operations.find(o => o.method === method && o.pathTemplate === path)!;
|
||||
const byPath = (method: string, path: string) =>
|
||||
upstream58e2204Operations.find((o) => o.method === method && o.pathTemplate === path)!;
|
||||
const r3 = [
|
||||
['DELETE','/api/esim/profiles/{iccid}'], ['DELETE','/api/call/history/{id}'], ['POST','/api/call/history/clear'],
|
||||
['POST','/api/sms/batch-delete'], ['DELETE','/api/sms/conversation/{phone_number}'], ['DELETE','/api/sms/message/{id}'], ['POST','/api/sms/clear'],
|
||||
['POST','/api/notifications/logs/clear'], ['POST','/api/notifications/queue/clear'], ['DELETE','/api/notifications/queue/{id}'],
|
||||
['POST','/api/automation/logs/clear'],
|
||||
['DELETE', '/api/esim/profiles/{iccid}'],
|
||||
['DELETE', '/api/call/history/{id}'],
|
||||
['POST', '/api/call/history/clear'],
|
||||
['POST', '/api/sms/batch-delete'],
|
||||
['DELETE', '/api/sms/conversation/{phone_number}'],
|
||||
['DELETE', '/api/sms/message/{id}'],
|
||||
['POST', '/api/sms/clear'],
|
||||
['POST', '/api/notifications/logs/clear'],
|
||||
['POST', '/api/notifications/queue/clear'],
|
||||
['DELETE', '/api/notifications/queue/{id}'],
|
||||
['POST', '/api/automation/logs/clear'],
|
||||
];
|
||||
for (const [method, path] of r3) {
|
||||
const operation = byPath(method, path);
|
||||
@@ -283,9 +555,11 @@ test('product risk floors and auth replay policy are enforced', () => {
|
||||
assert.ok(['explicit', 'strong'].includes(operation.confirmationPolicy));
|
||||
}
|
||||
const r2 = [
|
||||
['POST','/api/sms/send'], ['POST','/api/notifications/test/{channel}'],
|
||||
['POST','/api/notifications/queue/retry-all'], ['POST','/api/notifications/queue/{id}/retry'],
|
||||
['POST','/api/automation/test/{task_id}'],
|
||||
['POST', '/api/sms/send'],
|
||||
['POST', '/api/notifications/test/{channel}'],
|
||||
['POST', '/api/notifications/queue/retry-all'],
|
||||
['POST', '/api/notifications/queue/{id}/retry'],
|
||||
['POST', '/api/automation/test/{task_id}'],
|
||||
];
|
||||
for (const [method, path] of r2) {
|
||||
const operation = byPath(method, path);
|
||||
@@ -303,17 +577,34 @@ test('product risk floors and auth replay policy are enforced', () => {
|
||||
});
|
||||
|
||||
test('evidence matrix lists every registry operation', async () => {
|
||||
const documentPath = fileURLToPath(new URL('../../../docs/api/simadmin-upstream-58e2204.md', import.meta.url));
|
||||
const documentPath = fileURLToPath(
|
||||
new URL('../../../docs/api/simadmin-upstream-58e2204.md', import.meta.url),
|
||||
);
|
||||
const document = await readFile(documentPath, 'utf8');
|
||||
const rows = document.split('\n').filter(line => /^\| `(?:GET|POST|DELETE)` \|/.test(line));
|
||||
const rows = document.split('\n').filter((line) => /^\| `(?:GET|POST|DELETE)` \|/.test(line));
|
||||
assert.equal(rows.length, 117);
|
||||
for (const operation of upstream58e2204Operations) {
|
||||
const row = rows.find(row => row.includes(`\`${operation.method}\``) && row.includes(`\`${operation.pathTemplate}\``) && row.includes(`\`${operation.operationId}\``));
|
||||
const row = rows.find(
|
||||
(row) =>
|
||||
row.includes(`\`${operation.method}\``) &&
|
||||
row.includes(`\`${operation.pathTemplate}\``) &&
|
||||
row.includes(`\`${operation.operationId}\``),
|
||||
);
|
||||
assert.ok(row, key(operation));
|
||||
const evidence = evidenceFixture.operations.find((e: any) => `${e.method} ${e.path} ${e.handler}` === operationKey(operation));
|
||||
const evidence = evidenceFixture.operations.find(
|
||||
(e: any) => `${e.method} ${e.path} ${e.handler}` === operationKey(operation),
|
||||
);
|
||||
assert.ok(evidence, operation.operationId);
|
||||
for (const value of [operation.upstreamDomain, operation.uiOwner, operation.handler, operation.requestContentType,
|
||||
operation.idempotency, operation.riskLevel, operation.capability, evidence.sha256.slice(0, 16)]) {
|
||||
for (const value of [
|
||||
operation.upstreamDomain,
|
||||
operation.uiOwner,
|
||||
operation.handler,
|
||||
operation.requestContentType,
|
||||
operation.idempotency,
|
||||
operation.riskLevel,
|
||||
operation.capability,
|
||||
evidence.sha256.slice(0, 16),
|
||||
]) {
|
||||
assert.ok(row.includes(value), `${operation.operationId}: document drift for ${value}`);
|
||||
}
|
||||
}
|
||||
@@ -321,8 +612,12 @@ test('evidence matrix lists every registry operation', async () => {
|
||||
assert.match(document, /Phase 0\.3[^\n]*真实响应样本/);
|
||||
assert.match(document, /dynamic-json/);
|
||||
assert.match(document, /58e220411d6599609f0eeda01eb7016e9212f970/);
|
||||
for (const operation of upstream58e2204Operations as any[]) for (const field of operation.sensitiveFields) {
|
||||
const exactRow = `| ${operation.method} | \`${operation.pathTemplate}\` | ${field.direction} | \`${field.path}\` | ${field.redactionMode} | ${field.reason} |`;
|
||||
assert.ok(document.includes(exactRow), `${operation.operationId}: sensitive ledger drift ${field.path}`);
|
||||
}
|
||||
for (const operation of upstream58e2204Operations as any[])
|
||||
for (const field of operation.sensitiveFields) {
|
||||
const exactRow = `| ${operation.method} | \`${operation.pathTemplate}\` | ${field.direction} | \`${field.path}\` | ${field.redactionMode} | ${field.reason} |`;
|
||||
assert.ok(
|
||||
document.includes(exactRow),
|
||||
`${operation.operationId}: sensitive ledger drift ${field.path}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user