82 lines
12 KiB
JSON
82 lines
12 KiB
JSON
{
|
|
"schemaVersion": 1,
|
|
"baselineCommit": "0d16a691e0249fe1264bf960de5e59eaa9b87fed",
|
|
"authorization": "Later explicit owner instruction permits these three source repairs; historical acceptance/results stay unchanged.",
|
|
"priorFailures": {
|
|
"source": "docs/redesign/phase-9/rd21-remediation/ACCEPTANCE_ADDENDUM_2026-10-23.md",
|
|
"diagnostics": {
|
|
"line": 85,
|
|
"passed": 16,
|
|
"failed": 2,
|
|
"exitCode": 1,
|
|
"cause": "data.spec.ts and sustained.spec.ts unversioned Career seeds expected200/received400"
|
|
},
|
|
"volume": {
|
|
"line": 86,
|
|
"passed": 2,
|
|
"failed": 1,
|
|
"exitCode": 1,
|
|
"cause": "CV-volume Career seed expected200/received400"
|
|
},
|
|
"rawEvidence": "docs/redesign/phase-9/rd21-remediation/validation.json",
|
|
"disposition": "Actual failures preserved; do not relabel prior runs"
|
|
},
|
|
"newExecutions": {
|
|
"owner": "coordinator",
|
|
"diagnostics": {
|
|
"passed": 18,
|
|
"failed": 0,
|
|
"skipped": 0,
|
|
"evidence": ".artifacts/mariadb/browser-historical.json"
|
|
},
|
|
"volume": {
|
|
"passed": 3,
|
|
"failed": 0,
|
|
"skipped": 0,
|
|
"evidence": ".artifacts/mariadb/browser-volume.json"
|
|
},
|
|
"provider": "explicit transitional SQLite; not MariaDB qualification",
|
|
"record": "validation.json"
|
|
},
|
|
"preservedAssertions": [
|
|
"250 jobs; eight-role long CV;240 reads over>=60000ms;20 navigations;exact final profile",
|
|
"100 CV revisions/exact history/stale rejection/additive restore/canonical unchanged",
|
|
"100 timeline messages/exact membership/content/tenant denial",
|
|
"100 notifications/bounded lists/unread counts/idempotent read/tenant denial"
|
|
],
|
|
"strengthenedAssertions": [
|
|
"Equal A/B source snapshots before either writes",
|
|
"Each replacement carries its own originally read source revision",
|
|
"A commit advances revision and persists first edit",
|
|
"Stale B returns exactly409 for Career and job",
|
|
"Exact winner snapshots and Career history unchanged after B rejection",
|
|
"Lost-first-edit booleans must both be false"
|
|
],
|
|
"noWeakening": {
|
|
"skipsAdded": false,
|
|
"timeoutChanges": false,
|
|
"retriesAdded": false,
|
|
"historicalResultsModified": false
|
|
},
|
|
"files": [
|
|
{
|
|
"file": "docs/redesign/phase-9/p9-05/diagnostics/data.spec.ts",
|
|
"baselineSha256": "cb85a99f7af8094a341a2c44073e849924f0261ed63be3e643cb510c9f59c1e6",
|
|
"updatedSha256": "b33a99c3b280f7cac8ed4294e0a4a0a297f4761708e5414e3bf768f8146a7780",
|
|
"delta": "--- 0d16a691e0249fe1264bf960de5e59eaa9b87fed/docs/redesign/phase-9/p9-05/diagnostics/data.spec.ts\n+++ working-tree/docs/redesign/phase-9/p9-05/diagnostics/data.spec.ts\n@@ -9,7 +9,7 @@\n return { 'X-CSRF-TOKEN': csrf, 'X-Session-Context': me.sessionContext as string };\n }\n \n-test('P9 characterize retained RD21 stale writes separately from CV revision safety', async ({ playwright }, info) => {\n+test('P9 RD21 source revisions reject stale writes and preserve acknowledged winners', async ({ playwright }, info) => {\n const admin = await playwright.request.newContext();\n const first = await playwright.request.newContext();\n const second = await playwright.request.newContext();\n@@ -19,31 +19,47 @@\n expect((await admin.post(`${api}/users`, { headers: adminHeaders, data: { email, password, roles: [] } })).status()).toBe(200);\n const a = await login(first, email); const b = await login(second, email);\n const submitted = { version: '1', contact: { fullName: 'Synthetic concurrent owner' }, summary: ['Initial summary'] };\n- expect((await first.put(`${api}/career/profile`, { headers: a, data: { profile: submitted } })).status()).toBe(200);\n- const careerA = (await (await first.get(`${api}/career/profile`)).json()).profile;\n- const careerB = (await (await second.get(`${api}/career/profile`)).json()).profile;\n+ const initialCareer = await (await first.get(`${api}/career/profile`)).json();\n+ expect((await first.put(`${api}/career/profile`, { headers: a, data: { expectedVersion: initialCareer.revision, profile: submitted } })).status()).toBe(200);\n+ const sourceA = await (await first.get(`${api}/career/profile`)).json();\n+ const sourceB = await (await second.get(`${api}/career/profile`)).json();\n+ expect(sourceB).toEqual(sourceA);\n+ const careerA = sourceA.profile; const careerB = sourceB.profile;\n careerA.summary = ['First editor committed summary']; careerB.contact.headline = 'Second editor headline';\n- expect((await first.put(`${api}/career/profile`, { headers: a, data: { profile: careerA } })).status()).toBe(200);\n- const staleCareer = await second.put(`${api}/career/profile`, { headers: b, data: { profile: careerB } });\n- expect([200, 409]).toContain(staleCareer.status());\n- const careerAfter = (await (await first.get(`${api}/career/profile`)).json()).profile;\n+ expect((await first.put(`${api}/career/profile`, { headers: a, data: { expectedVersion: sourceA.revision, profile: careerA } })).status()).toBe(200);\n+ const careerWinner = await (await first.get(`${api}/career/profile`)).json();\n+ expect(careerWinner.revision).toBe(sourceA.revision + 1);\n+ expect(careerWinner.profile.summary).toEqual(careerA.summary);\n+ const careerHistory = await (await first.get(`${api}/career/profile/versions`)).json();\n+ const staleCareer = await second.put(`${api}/career/profile`, { headers: b, data: { expectedVersion: sourceB.revision, profile: careerB } });\n+ expect(staleCareer.status()).toBe(409);\n+ const careerReadback = await (await second.get(`${api}/career/profile`)).json();\n+ expect(careerReadback).toEqual(careerWinner);\n+ expect(await (await first.get(`${api}/career/profile/versions`)).json()).toEqual(careerHistory);\n+ const careerAfter = careerReadback.profile;\n const company = await first.post(`${api}/companies`, { headers: a, data: { name: 'Synthetic stale job fixture' } });\n expect(company.status()).toBe(201);\n const created = await first.post(`${api}/jobapplications`, { headers: a, data: { companyId: (await company.json()).id, jobTitle: 'Original job title', status: 'Saved' } });\n expect(created.status()).toBe(201); const jobId = (await created.json()).id;\n const jobA = await (await first.get(`${api}/jobapplications/${jobId}`)).json();\n const jobB = await (await second.get(`${api}/jobapplications/${jobId}`)).json();\n+ expect(jobB).toEqual(jobA);\n jobA.jobTitle = 'First editor committed title'; jobB.notes = 'Second editor notes';\n- expect([200, 204]).toContain((await first.put(`${api}/jobapplications/${jobId}`, { headers: a, data: jobA })).status());\n- const staleJob = await second.put(`${api}/jobapplications/${jobId}`, { headers: b, data: jobB });\n- expect([200, 204, 409]).toContain(staleJob.status());\n- const jobAfter = await (await first.get(`${api}/jobapplications/${jobId}`)).json();\n+ expect((await first.put(`${api}/jobapplications/${jobId}`, { headers: a, data: { ...jobA, expectedVersion: jobA.version } })).status()).toBe(204);\n+ const jobWinner = await (await first.get(`${api}/jobapplications/${jobId}`)).json();\n+ expect(jobWinner.version).toBe(jobA.version + 1);\n+ expect(jobWinner.jobTitle).toBe(jobA.jobTitle);\n+ const staleJob = await second.put(`${api}/jobapplications/${jobId}`, { headers: b, data: { ...jobB, expectedVersion: jobB.version } });\n+ expect(staleJob.status()).toBe(409);\n+ const jobAfter = await (await second.get(`${api}/jobapplications/${jobId}`)).json();\n+ expect(jobAfter).toEqual(jobWinner);\n const careerLostFirstEdit = careerAfter.summary[0] !== careerA.summary[0];\n const jobLostFirstEdit = jobAfter.jobTitle !== jobA.jobTitle;\n+ expect(careerLostFirstEdit).toBe(false); expect(jobLostFirstEdit).toBe(false);\n await info.attach('phase9-rd21-characterization', { body: JSON.stringify({ id: 'RD21',\n career: { staleStatus: staleCareer.status(), expectedFirstSummary: careerA.summary, actualSummary: careerAfter.summary, lostFirstEdit: careerLostFirstEdit },\n job: { staleStatus: staleJob.status(), expectedFirstTitle: jobA.jobTitle, actualTitle: jobAfter.jobTitle, lostFirstEdit: jobLostFirstEdit },\n- safetyQualification: careerLostFirstEdit || jobLostFirstEdit ? 'FAIL: stale writes can overwrite acknowledged edits' : 'Finite scenario retained prior edits',\n- limitation: 'This passing characterization records observed behavior; it does not accept unsafe stale writes or provide a new revision contract. Creation-intent idempotency remains separate.' }, null, 2), contentType: 'application/json' });\n+ safetyQualification: 'Both stale replacements rejected with 409; exact winner state and Career history retained',\n+ limitation: 'Finite sequential A/B scenario on the configured isolated API; not simultaneous provider contention or creation-intent idempotency proof.' }, null, 2), contentType: 'application/json' });\n } finally { await second.dispose(); await first.dispose(); await admin.dispose(); }\n });\n"
|
|
},
|
|
{
|
|
"file": "docs/redesign/phase-9/p9-05/diagnostics/sustained.spec.ts",
|
|
"baselineSha256": "59a87155e46f27d919549efc402b534ede632aad01ede4194601da4fd6875270",
|
|
"updatedSha256": "76db0ac115e4745b67186b116421e2d7c374bd2e8f87a2fd40f04215e7b463c2",
|
|
"delta": "--- 0d16a691e0249fe1264bf960de5e59eaa9b87fed/docs/redesign/phase-9/p9-05/diagnostics/sustained.spec.ts\n+++ working-tree/docs/redesign/phase-9/p9-05/diagnostics/sustained.spec.ts\n@@ -29,7 +29,7 @@\n const profile = { version: '1', contact: { fullName: '\u00c3\u2026se Synthetic', email: 'synthetic@example.test' }, summary: ['Documented accessible workflows.'],\n jobs: Array.from({ length: 8 }, (_, index) => ({ id: `p9-role-${index}`, title: `Platform engineer ${index}`, company: 'Nordlys synthetic',\n start: '2017-01', end: '2025-12', bullets: Array.from({ length: 6 }, (_, bullet) => `Evidence ${index}.${bullet}: ${'Documented reliable workflows. Dokumenterte p\u00c3\u00a5litelige arbeidsflyter. '.repeat(3).trim()}`) })) };\n- expect((await page.request.put(`${api}/career/profile`, { headers, data: { profile } })).status()).toBe(200);\n+ expect((await page.request.put(`${api}/career/profile`, { headers, data: { expectedVersion: (await (await page.request.get(`${api}/career/profile`)).json()).revision, profile } })).status()).toBe(200);\n const savedProfile = (await (await page.request.get(`${api}/career/profile`)).json()).profile;\n expect(savedProfile.jobs).toHaveLength(8);\n expect(savedProfile.jobs.map((job: { start: string; end: string; bullets: string[] }) => ({ start: job.start, end: job.end, bullets: job.bullets })))\n"
|
|
},
|
|
{
|
|
"file": "docs/redesign/phase-9/p9-05/volume/volume.spec.ts",
|
|
"baselineSha256": "4510f29298f6391b36d27d2634b1f9355802eafcc310488231014fab799a4108",
|
|
"updatedSha256": "9a3abd9f4a8fe558e643bf481dc9a0d1857975e9d532fda6c95e27cf437f7908",
|
|
"delta": "--- 0d16a691e0249fe1264bf960de5e59eaa9b87fed/docs/redesign/phase-9/p9-05/volume/volume.spec.ts\n+++ working-tree/docs/redesign/phase-9/p9-05/volume/volume.spec.ts\n@@ -26,7 +26,7 @@\n const account = await owner(playwright);\n try {\n const { api, headers } = account;\n- expect((await api.put(`${base}/career/profile`, { headers, data: { profile: { version: '1', contact: { fullName: 'Volume fixture' }, summary: ['Canonical unchanged'] } } })).status()).toBe(200);\n+ expect((await api.put(`${base}/career/profile`, { headers, data: { expectedVersion: (await (await api.get(`${base}/career/profile`)).json()).revision, profile: { version: '1', contact: { fullName: 'Volume fixture' }, summary: ['Canonical unchanged'] } } })).status()).toBe(200);\n const source = await (await api.get(`${base}/career/profile`)).json();\n const created = await api.post(`${base}/cv/variants`, { headers, data: { name: 'Volume CV', settings: { themeId: 'minimal' } } });\n expect(created.status()).toBe(200); let cv = await created.json(); const id = cv.id;\n"
|
|
}
|
|
]
|
|
}
|