import test from 'node:test'; 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 { 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());}; 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: 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('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();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);});