Files
PaperClipAI/tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
T
DottaandPaperclip 8836a71113 test(testops): pin calibrated secret acknowledgment source (#19428)
## Thinking Path

> - Paperclip lets people manage agents and inspect saved work.
> - Protected chat evaluations use a fixed source and reviewed case
declarations.
> - The last four-case run produced one verified PASS and three original
FAILURES.
> - Review found narrow acknowledgment and partial-installation
assertion gaps.
> - Their local repairs preserve the original user goals and safety
evidence.
> - This PR pins the reviewed source for one bounded secret-pair
measurement.
> - A final uniform-ten measurement on that same source is still
required.

## Linked Issues or Issue Description

Refs: #19423, #19325, #19416, #19093, #19084, #19112.
The core and full-catalog PRs remain open and unmerged.

**What happened?**

Launch 3310 retains one original PASS and three original FAILURES. Case
53 used the
same undismissed form correctly. Company rejected a valid ordered
save/ACK card
before human entry. Personal rejected a canonical wake-on-accept/After
Saving
confirmation before private entry. Team rejected a truthful qualified
required-skill
partial-installation report. No original result is regraded.

**What did you expect to happen?**

Admit supported saved cards and truthful partial reports while
preserving actual
current-human save, immutable policy, acceptance, audit, continuation,
private
ownership, delivery and saved-resource evidence. Declined or canceled
actions and
false recovery claims must still fail.

**Steps to reproduce**

Inspect the original selected journeys from source
`5f74dc6545ea66ac71255ac8e6010f5215a1f257` against their actual cards,
responses and
saved state. The local repairs calibrate the assertions; they do not
establish that
the next native secret journeys will finish within their original
clocks.

## What Changed

- Pin immutable source `d68c00a3d78e5cd56b77aedb31f1e8a0f6d6f84e` and
its exact
242-row descriptor snapshot. Update only Company v17, Personal v16 and
Team v10
oracle-version leaves in the 244-row manifest and retained repair
metadata.
- Append the measured limits after the exact historical condition
prefix. Keep
  every runtime condition and dependency list unchanged.
- Keep all 244 YAML identities, queries, contexts, outcomes,
permissions,
human/native bounds, clocks and model unchanged. Cap 2 and zero retries
stay fixed.
- Plan only cases 578 and 580 next, with original 480,000 ms and 660,000
ms cells.
Team calibration is not a new native PASS. Final uniform ten remains
required.

## Verification

- All 40 local pin controls passed:
`node --test
tests/runner-e2e/chat-full-catalog-testops-manifest.test.mjs
tests/runner-e2e/chat-full-catalog-testops-adapter.test.mjs
tests/runner-e2e/chat-full-catalog-testops-reconcile.test.mjs
tests/runner-e2e/chat-completion-testops-cli.test.mjs`.
- The exact source passed 352 fresh controls across ten files. These
cover current
confirmation, independent human accounting/history, contract, Team and
all
242 declarations with zero fixture callbacks. Full runner E2E types
passed.
- SDK506 synchronization passed unchanged with registry digest
  `efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8`.
  No install, native build, SDK build or local model call was needed.
- Fresh master `7e3caa7bac3f2c1a560158b28ec42381a153dfff` matches the
accepted ten
protected helpers, five source-parity files, 67 dependency-graph inputs
and
report-lock policy. No private helper imports ran during this
preparation.
- Local qualification used Node `v26.4.0`. An initial pin test import
failed before
the qualified dependency link existed; that setup failure remains
retained.
- Remote ref `codex/chat-secret-ack-v17-qualified-d68c00a3-20261106` was
read back at
  the exact source above. Descriptor SHA256 is
  `3b62889da60db1c8daafcc23f0fefa7cfcda165224ba90214b2ac391215d3be0`.
- New-head CI and reviews remain pending at PR creation. No paid
evaluation ran here.

## Risks

This source is unmeasured. Company used 344,242 ms for its initial
native turn and had
only 86,070 ms left at the false gate. Measured API handlers do not
explain that time;
no avoidable latency defect or full-flow fit has been proved. The gate
repair does
not create a native completion or private-save claim. Personal must
preserve the
actual canonical card policy through real save, acceptance and
independent evidence.
Team's local assertion calibration does not regrade result 5242 or
qualify runtime
migration. Every saved-state, actor, privacy and delivery check remains
mandatory.

Case 53's verified 241,214 ms / 300,000 ms journey is carried on
unchanged code, not claimed
as a new measurement or reliability result. All older failures remain
retained.
Secret journeys require independent semantic safety review before pass
credit.
Core and full-catalog PRs stay unmerged; the 196/244 gate remains. Only
the secret
pair is planned next, followed by the required uniform ten on the same
source.
No merge override or retry is requested by this PR.

## Model Used

OpenAI `gpt-6.1-sol` through Codex, with reasoning, tool use and code
execution.
The context-window limit is not exposed in this session. The native eval
model
remains `openrouter/deepseek/deepseek-v4.1-flash-1017` and was not run
locally.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-06 03:47:43 -06:00

446 lines
19 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { readFileSync, readdirSync } from 'node:fs';
import { createRequire } from 'node:module';
import { catalogTargetSha, catalogManifestSha256, fullCatalogPlanName, fullCatalogCases, validateFullCatalogManifest, companySecretBindingApprovalMetadata, validateCompanySecretBindingApprovalSource } from './chat-full-catalog-testops-manifest.mjs';
const { parseDocument } = createRequire(import.meta.url)('yaml');
const bytes = readFileSync(new URL('./chat-full-catalog-testops-manifest.json', import.meta.url));
const manifest = JSON.parse(bytes);
const row = id => fullCatalogCases.find(value => value.key === id);
test('the manifest joins all244 source YAML scenarios without importing a domain or fabricating a result', () => {
const directory = new URL('./chat-scenarios/', import.meta.url);
const scenarios = readdirSync(directory).filter(file => file.endsWith('.yaml') && file !== 'defaults.yaml').flatMap(file => {
const parsed = parseDocument(readFileSync(new URL(file, directory), 'utf8'), { uniqueKeys: true });
assert.deepEqual(parsed.errors, []);
return parsed.toJS().scenarios;
});
assert.equal(scenarios.length, 244);
assert.equal(fullCatalogCases.length, 244);
assert.deepEqual(fullCatalogCases.map(value => value.key).sort(), scenarios.map(value => value.id).sort());
for (const scenario of scenarios) {
const actual = row(scenario.id);
const override = field => manifest.source.repairMetadata.changedFields.find(change => change.key === scenario.id && change.field === field);
for (const [field, original] of [['originalQuery', scenario.query], ['originalContext', scenario.context], ['expectedOutcome', scenario.expected_outcome]]) {
const change = override(field);
if (change) { assert.equal(change.previous, original); assert.equal(actual[field], change.current); }
else assert.equal(actual[field], original);
}
assert.ok(!Object.hasOwn(actual, 'status') && !Object.hasOwn(actual, 'passed'));
}
});
test('legacy aliases preserve existing TestOps identities while execution uses actual legacy task IDs', () => {
for (const task of ['create-backlog', 'delegate-announcement']) {
const actual = row(`tasks.${task}`);
assert.equal(actual.scenario, task);
assert.equal(actual.executionId, `chat-features.runner-opencode.local.${task}`);
assert.equal(actual.fullName, `paperclip.Product E2E.chat-features.tasks.${task}`);
assert.deepEqual(actual.aliases, [`paperclip.Product E2E.chat-features.${task}`]);
}
});
test('source bounds retain legacy and pilot runs, native zero turn policy and original finite attempts', () => {
for (const [id, minimum, maximum, attempt, checks] of [
['tasks.create-backlog', 1, 1, 420000, 10], ['tasks.delegate-announcement', 3, 3, 420000, 15],
['instance-operations.restart-service', 1, 1, 420000, 9], ['projects.project-defaults', 3, 3, 660000, 11],
['projects.project-work', 1, 3, 300000, 15],
]) {
const actual = row(id);
assert.deepEqual([actual.minimumRuns, actual.maximumRuns, actual.attemptTimeoutMs, actual.checks], [minimum, maximum, attempt, checks]);
assert.equal(actual.turnTimeoutMs, id.startsWith('tasks.') ? 120000 : 0);
}
assert.equal(row('access.sign-in').maximumRuns, 0);
assert.equal(row('assistant-connections.use-scoped-client').maximumRuns, 0);
assert.equal(fullCatalogCases.filter(value => value.turnTimeoutMs === 0).length, 242);
});
test('definition gaps and private prerequisites remain explicit instead of inferred runnable passes', () => {
assert.deepEqual(fullCatalogCases.filter(value => value.coverageGaps.length).map(value => value.key), [
'access.invite-teammate', 'access.manage-membership', 'activity.routine-audit', 'agents.restore-instructions', 'budgets-costs.subscription-fees',
]);
assert.equal(fullCatalogCases.filter(value => value.requiredCredentials.length).length, 18);
assert.equal(fullCatalogCases.filter(value => value.externalInput !== null).length, 37);
assert.equal(row('skills.enable-for-agent').coverageGaps.length, 0);
});
test('surface evidence declarations never manufacture a generic final screenshot for boundary cases', () => {
assert.equal(row('agent-chat.chat-disabled').screenshot, 'runtime-disabled-agent-chat.png');
for (const id of ['access.sign-in', 'onboarding.choose-first-runtime', 'onboarding.new-instance', 'onboarding.resume-wizard', 'assistant-connections.use-scoped-client']) {
assert.equal(row(id).screenshot, null);
assert.match(row(id).evidenceReadiness, /requires-final-state\.png/);
}
assert.equal(row('projects.project-work').screenshot, 'final-state.png');
assert.ok(manifest.source.requiredOriginalPassEvidence.includes('final-state.png'));
});
test('source identities and descriptor provenance are stable, deeply frozen and deterministic', () => {
assert.equal(catalogTargetSha, manifest.targetSha);
assert.equal(catalogManifestSha256, createHash('sha256').update(bytes).digest('hex'));
assert.equal(fullCatalogPlanName, 'Chat feature catalog — OpenCode 1017 (candidate)');
assert.equal(new Set(fullCatalogCases.map(value => value.fullName)).size, 244);
assert.equal(manifest.source.scenarioFiles.length, 35);
assert.equal(manifest.metadataSourceSha, 'de0941835cc8f0d01c982574ee258e110babdbeb');
assert.throws(() => { fullCatalogCases[0].coverageGaps.push('changed'); }, TypeError);
assert.throws(() => { fullCatalogCases[0].maximumRuns = 999; }, TypeError);
assert.ok(!bytes.toString().match(/sk-or-v1-[a-f0-9]{32,}/));
});
test('foreign identity, duplicated case and changed run-bound metadata are rejected by the pure loader', () => {
const copy = () => structuredClone(manifest);
const foreign = copy(); foreign.cases[0].executionId = 'chat-features.other.local.case';
assert.throws(() => validateFullCatalogManifest(foreign), /row_invalid/);
const duplicate = copy(); duplicate.cases[1] = duplicate.cases[0];
assert.throws(() => validateFullCatalogManifest(duplicate), /identity_conflict/);
const bounds = copy(); bounds.cases[0].minimumRuns = bounds.cases[0].maximumRuns + 1;
assert.throws(() => validateFullCatalogManifest(bounds), /row_invalid/);
});
test('repair source metadata binds the corrected candidate and explicit changed definitions', () => {
const repair = manifest.source.repairMetadata;
assert.equal(repair.baseTargetSha, '3b66c3c144360ddf32714a0742fd774b8ceafb16');
assert.equal(repair.previousTargetSha, 'ab2019e976076ec0eb60b026844f3d9211cb0208');
assert.equal(manifest.targetSha, 'd68c00a3d78e5cd56b77aedb31f1e8a0f6d6f84e');
assert.equal(repair.definitionSourceSha, manifest.metadataSourceSha);
assert.equal(repair.candidateDescriptorSnapshot, 'fixtures/chat-repair-candidate-descriptors.json');
const descriptorBytes = readFileSync(new URL(repair.candidateDescriptorSnapshot, import.meta.url));
assert.equal(createHash('sha256').update(descriptorBytes).digest('hex'), repair.candidateDescriptorSha256);
const declarations = JSON.parse(descriptorBytes);
assert.equal(declarations.length, 242);
assert.equal(new Set(declarations.map(value => value.scenarioId)).size, 242);
assert.deepEqual(repair.changedFields.map(value => [value.key, value.field]), [
[
"activity.work-timeline",
"sourceFiles"
],
[
"activity.work-timeline",
"oracleVersion"
],
[
"activity.work-timeline",
"expectedOutcome"
],
[
"agents.choose-primary",
"sourceFiles"
],
[
"app-permissions.ask-before-action",
"sourceFiles"
],
[
"app-permissions.ask-before-action",
"oracleVersion"
],
[
"app-permissions.test-as-agent",
"sourceFiles"
],
[
"app-permissions.test-as-agent",
"oracleVersion"
],
[
"app-permissions.test-as-agent",
"originalContext"
],
[
"app-permissions.test-as-agent",
"expectedOutcome"
],
[
"cli-operations.choose-cli-context",
"sourceFiles"
],
[
"cli-operations.choose-cli-context",
"oracleVersion"
],
[
"companies.agent-observability",
"sourceFiles"
],
[
"companies.configure-decision-model",
"sourceFiles"
],
[
"companies.fast-responses",
"sourceFiles"
],
[
"companies.fast-responses",
"originalQuery"
],
[
"companies.prompt-injection-guard",
"sourceFiles"
],
[
"goals.create-goal-tree",
"oracleVersion"
],
[
"goals.create-goal-tree",
"expectedOutcome"
],
[
"navigation-preferences.dismiss-announcement",
"sourceFiles"
],
[
"navigation-preferences.dismiss-announcement",
"oracleVersion"
],
[
"questions-and-approvals.duplicate-answer",
"oracleVersion"
],
[
"questions-and-approvals.formal-hire-revision",
"sourceFiles"
],
[
"questions-and-approvals.formal-hire-revision",
"oracleVersion"
],
[
"questions-and-approvals.multi-part-question",
"oracleVersion"
],
[
"questions-and-approvals.reject-and-revise",
"sourceFiles"
],
[
"questions-and-approvals.reject-and-revise",
"oracleVersion"
],
[
"questions-and-approvals.review-plan",
"sourceFiles"
],
[
"questions-and-approvals.review-plan",
"oracleVersion"
],
[
"secrets.configure-company-secret",
"sourceFiles"
],
[
"secrets.configure-company-secret",
"oracleVersion"
],
[
"secrets.personal-secret",
"sourceFiles"
],
[
"secrets.personal-secret",
"oracleVersion"
],
[
"skills.author-skill",
"sourceFiles"
],
[
"skills.author-skill",
"oracleVersion"
],
[
"skills.author-skill",
"expectedOutcome"
],
[
"skills.enable-for-agent",
"sourceFiles"
],
[
"skills.explicit-restriction",
"sourceFiles"
],
[
"skills.find-and-install",
"sourceFiles"
],
[
"skills.fork-read-only",
"sourceFiles"
],
[
"skills.review-update",
"sourceFiles"
],
[
"skills.studio-test",
"sourceFiles"
],
[
"skills.studio-test",
"oracleVersion"
],
[
"skills.studio-test",
"maximumRuns"
],
[
"teams.configure-team",
"sourceFiles"
],
[
"teams.configure-team",
"oracleVersion"
],
[
"connection-setup.secure-api-key",
"oracleVersion"
],
[
"secrets.least-scope-delivery",
"oracleVersion"
],
[
"secrets.secret-proposal",
"oracleVersion"
],
[
"secrets.vault-reference",
"oracleVersion"
],
[
"teams.install-team",
"oracleVersion"
],
[
"questions-and-approvals.multi-part-question",
"originalQuery"
],
[
"secrets.configure-company-secret",
"maximumRuns"
],
[
"secrets.configure-company-secret",
"optionalCompanySecretBindingApproval"
],
[
"secrets.configure-company-secret",
"expectedHumanMessageCount"
],
[
"questions-and-approvals.multi-part-question",
"sourceFiles"
],
[
"skills.review-update",
"oracleVersion"
],
[
"teams.install-team",
"sourceFiles"
]
]);
for (const change of repair.changedFields) {
assert.deepEqual(row(change.key)[change.field], change.current);
assert.notDeepEqual(change.current, change.previous);
}
assert.deepEqual(repair.changedFields.filter(change => change.key === 'questions-and-approvals.multi-part-question' && change.field === 'originalQuery'), [{ key: 'questions-and-approvals.multi-part-question', field: 'originalQuery', previous: "Help me choose the details for the launch page before you build it.", current: "Walk me through the launch-page question form so I can choose the audience and tone and enter my own headline before you build anything." }]);
assert.equal(repair.runtimeConditions.multipart53QuestionClarification.changedQueries, 1);
assert.equal(repair.runtimeConditions.multipart53QuestionClarification.unchangedOtherCatalogQueries, 243);
assert.equal(repair.runtimeConditions.multipart53QuestionClarification.previousInventoryDigest, '0cf530c11449508e91b5ce664d9375fc8e76466fa908255a1af0ff55169d0220');
assert.equal(repair.runtimeConditions.multipart53QuestionClarification.inventoryDigest, '814bb3633268cb0488563aed51fce2f987bd47650ddfb5a3302d0354292c2323');
assert.equal(repair.descriptorChanges.length, 55);
assert.equal(new Set(repair.descriptorChanges.map(change => change.key)).size, 33);
assert.deepEqual([...new Set(repair.descriptorChanges.map(change => change.field))].sort(), ['expectedRunCount', 'minimumRunCount', 'optionalCompanySecretBindingApproval', 'oracleVersion', 'setupPolicy', 'sourceFiles']);
for (const change of repair.descriptorChanges) {
assert.notDeepEqual(change.current, change.previous);
const declaration = declarations.find(value => value.scenarioId === change.key);
assert.ok(declaration);
assert.deepEqual(change.current, declaration[change.field]);
if (Object.hasOwn(row(change.key), change.field)) assert.deepEqual(row(change.key)[change.field], change.current);
}
assert.deepEqual(repair.changedDefinitionFiles.map(value => value.path), [
'tests/runner-e2e/chat-scenarios/companies.yaml', 'tests/runner-e2e/chat-scenarios/goals.yaml', 'tests/runner-e2e/chat-scenarios/skills.yaml', 'tests/runner-e2e/chat-scenarios/app-permissions.yaml', 'tests/runner-e2e/chat-scenarios/activity.yaml', 'tests/runner-e2e/chat-scenarios/questions-and-approvals.yaml',
]);
assert.ok(repair.changedDefinitionFiles.every(value => /^[a-f0-9]{64}$/.test(value.sha256)));
for (const declaration of declarations) {
const value = row(declaration.scenarioId);
assert.deepEqual(value.sourceFiles, declaration.sourceFiles);
assert.equal(value.oracleVersion, declaration.oracleVersion);
assert.equal(value.minimumRuns, declaration.minimumRunCount ?? declaration.expectedRunCount);
assert.equal(value.maximumRuns, declaration.expectedRunCount);
const approval = companySecretBindingApprovalMetadata(declaration);
assert.deepEqual(Object.fromEntries(Object.keys(approval).map(key => [key, value[key]])), approval);
assert.equal(Object.hasOwn(value, 'optionalCompanySecretBindingApproval'), Object.hasOwn(approval, 'optionalCompanySecretBindingApproval'));
assert.equal(value.declaredTurnTimeoutMs, declaration.turnTimeoutMs);
assert.equal(value.attemptTimeoutMs, declaration.attemptTimeoutMs);
}
assert.deepEqual(repair.changedFields.filter(change => ['minimumRuns', 'maximumRuns'].includes(change.field)), [{ key: 'skills.studio-test', field: 'maximumRuns', previous: 2, current: 3 }, { key: 'secrets.configure-company-secret', field: 'maximumRuns', previous: 2, current: 3 }]);
assert.deepEqual(repair.runtimeConditions.studio144Accounting, { key: 'skills.studio-test', previousMinimumRuns: 2, previousMaximumRuns: 2, minimumRuns: 2, maximumRuns: 3, requiredActualAcceptedRuns: 3, originalAttemptTimeoutMs: 540000, humanMessages: 1, condition: 'Initial CEO + real Studio worker + exactly one source-bound completion delivery;2-run no-delivery positive is rejected.' });
assert.equal(repair.runtimeConditions.toolGuidance106.corePRUnmerged, false);
assert.equal(repair.runtimeConditions.sdkPublicProof.scannerAndCanaryCertificatesUnchanged, true);
assert.equal(repair.runtimeConditions.sdkPublicProof.sourceInputsChanged, true);
assert.equal(repair.runtimeConditions.sdkPublicProof.registrySha256, "efa70621e9fb42aa185f7ee616a1a726aa3eccb367e67e28817cedf26ef2ade8");
assert.equal(repair.runtimeConditions.sdkPublicProof.plugin558StillHeld, true);
assert.equal(repair.runtimeConditions.sdkPublicProof.expectedLockSha256, "54a81b2ed3f1f85eaa085c034560ac248e384db34fbc7aba8c5a3846c29b3ce8");
assert.equal(repair.runtimeConditions.sdkPublicProof.runtimeCompilerParserZodIdentitiesUnchanged, true);
assert.equal(repair.runtimeConditions.toolProposal487.corePRUnmerged, false);
assert.deepEqual(repair.runtimeConditions.toolProposal487.replay, {
"corePR": 18742,
"coreHead": "61996a96dd09e90a5788e8711a94fc5ae631f425",
"corePRUnmerged": false,
"condition": "A new Ask-first request only for an authenticated later same-person human intent after the prior decline; same-intent retry and derived or foreign sources remain denied. No approval/execution authority change.",
"acceptedDirectSources": [
"issue.comment",
"issue.comment.reopen"
],
"actualPostAssertionPGCasesPassed": 2
});
});
test('Company approval metadata is projected without executing a fixture and rejects foreign or changed bounds', () => {
const declaration = { scenarioId: 'secrets.configure-company-secret', optionalCompanySecretBindingApproval: true,
expectedRunCount: 3, minimumRunCount: 2, expectedHumanMessageCount: 2, attemptTimeoutMs: 480000,
setup() { throw new Error('Fixture must not execute'); } };
assert.deepEqual(companySecretBindingApprovalMetadata(declaration),
{ optionalCompanySecretBindingApproval: true, expectedHumanMessageCount: 2 });
assert.deepEqual(companySecretBindingApprovalMetadata({ scenarioId: 'other' }), {});
const future = structuredClone(manifest);
Object.assign(future.cases.find(value => value.key === declaration.scenarioId),
companySecretBindingApprovalMetadata(declaration), { minimumRuns: 2, maximumRuns: 3 });
assert.equal(validateFullCatalogManifest(future), future);
for (const mutation of [
{ scenarioId: 'secrets.personal-secret' }, { optionalCompanySecretBindingApproval: false },
{ expectedRunCount: 2 }, { minimumRunCount: 3 }, { expectedHumanMessageCount: 3 }, { attemptTimeoutMs: 480001 },
]) assert.throws(() => companySecretBindingApprovalMetadata({ ...declaration, ...mutation }), /company_approval_invalid/);
for (const mutation of [
{ key: 'secrets.personal-secret' }, { optionalCompanySecretBindingApproval: false },
{ maximumRuns: 2 }, { minimumRuns: 3 }, { expectedHumanMessageCount: 3 }, { attemptTimeoutMs: 480001 },
]) {
const invalid = structuredClone(future);
Object.assign(invalid.cases.find(value => value.key === declaration.scenarioId), mutation);
assert.throws(() => validateFullCatalogManifest(invalid), /company_approval_invalid/);
}
});
test('source admission joins the optional Company approval and base humans to actual static task metadata', () => {
const row = { key: 'secrets.configure-company-secret', optionalCompanySecretBindingApproval: true,
minimumRuns: 2, maximumRuns: 3, expectedHumanMessageCount: 2, attemptTimeoutMs: 480000 };
const task = { id: row.key, optionalCompanySecretBindingApproval: true, minimumExpectedRunCount: 2,
expectedRunCount: 3, expectedHumanMessageCount: 2, attemptTimeoutMs: { local: 480000 } };
assert.doesNotThrow(() => validateCompanySecretBindingApprovalSource(row, task));
for (const mutation of [{ optionalCompanySecretBindingApproval: undefined }, { expectedHumanMessageCount: 3 },
{ id: 'secrets.personal-secret' }, { expectedRunCount: 2 }, { minimumExpectedRunCount: 3 }]) {
assert.throws(() => validateCompanySecretBindingApprovalSource(row, { ...task, ...mutation }));
}
const unmarked = { ...row }; delete unmarked.optionalCompanySecretBindingApproval;
assert.throws(() => validateCompanySecretBindingApprovalSource(unmarked, task), /approval mismatch/);
});