Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions packages/spec/src/automation/approval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ describe('ORG_MEMBERSHIP_LEVELS derives from BUILTIN_MEMBERSHIP_ROLES', () => {
// record-backed kinds MUST match the engine's resolution semantics
// (`plugin-approvals` resolveApproverSpec / expand*Users) — these assertions
// pin the object names and stored fields the engine actually queries.
describe('APPROVER_VALUE_BINDINGS (#3508)', () => {
describe('APPROVER_VALUE_BINDINGS — an approver value is picked from the records the engine resolves', () => {
it('covers every ApproverType member', () => {
for (const t of ApproverType.options) {
expect(APPROVER_VALUE_BINDINGS[t]).toBeDefined();
Expand Down Expand Up @@ -173,7 +173,7 @@ describe('APPROVER_VALUE_BINDINGS (#3508)', () => {
// #3508 follow-up: `xRef.map` names a picker KIND but never said where that
// picker's candidates live — which is how the designer came to query the
// metadata registry for data records. The data contract now ships on the wire.
describe('APPROVER_VALUE_SOURCES (#3508 follow-up)', () => {
describe('APPROVER_VALUE_SOURCES — where each picker finds its candidates, published on the wire', () => {
it('covers every ApproverType member, exactly like the bindings it projects', () => {
for (const t of ApproverType.options) {
expect(APPROVER_VALUE_SOURCES[t], `no source published for '${t}'`).toBeDefined();
Expand Down Expand Up @@ -309,7 +309,7 @@ describe('ApprovalNodeConfigSchema', () => {
expect(() => ApprovalNodeConfigSchema.parse({ ...minimal, behavior: 'weighted' })).toThrow();
});

it('accepts quorum / per_group behaviors with minApprovals and grouped approvers (#3266)', () => {
it('accepts quorum / per_group behaviors with minApprovals and grouped approvers', () => {
const quorum = ApprovalNodeConfigSchema.parse({ ...minimal, behavior: 'quorum', minApprovals: 2 });
expect(quorum.behavior).toBe('quorum');
expect(quorum.minApprovals).toBe(2);
Expand Down Expand Up @@ -486,7 +486,7 @@ describe('ApprovalEscalationSchema', () => {
// escalation block exists at all; within a block carrying timeoutHours,
// escalation is ON unless explicitly turned off. Declared in
// DEFAULT_CHANGES_BY_MAJOR (17) — this pin is what keeps the fingerprint honest.
it('defaults enabled to true — a block carrying timeoutHours is live unless explicitly off (#12278)', () => {
it('defaults enabled to true — a block carrying timeoutHours is live unless explicitly off', () => {
const omitted = ApprovalEscalationSchema.parse({ timeoutHours: 24 });
expect(omitted.enabled).toBe(true);
const explicit = ApprovalEscalationSchema.parse({ enabled: false, timeoutHours: 24 });
Expand All @@ -512,7 +512,7 @@ describe('normalizeDecisionOutputs', () => {
]);
});

it('carries the widget hints and the required flag through (objectui#2955)', () => {
it('carries the widget hints and the required flag through, for the decision dialog to render and enforce', () => {
expect(normalizeDecisionOutputs([
{ key: 'positions', label: 'Co-signers', type: 'position', multiple: true, required: true },
])).toEqual([
Expand Down Expand Up @@ -540,7 +540,7 @@ describe('normalizeDecisionOutputs', () => {
// a v17-new surface, tightened before stored volume exists. The published JSON
// schema now carries additionalProperties:false into the Studio form AND
// registerFlow()'s per-node config validation (#4027/#4040) — asserted below.
describe('unknown keys are rejected, not stripped (#4001)', () => {
describe('unknown keys are rejected, not stripped', () => {
const unknownKeyIssue = (schema: { safeParse: (v: unknown) => any }, value: unknown) => {
const result = schema.safeParse(value);
expect(result.success).toBe(false);
Expand Down Expand Up @@ -607,7 +607,7 @@ describe('unknown keys are rejected, not stripped (#4001)', () => {
* cases fail loudly instead, which is what "declared = enforced" needs on
* the declaring side.
*/
it('accepts `required` — the key the runtime enforces (#4525)', () => {
it('accepts `required` — the key the runtime enforces', () => {
expect(DecisionOutputDefSchema.parse({ key: 'next_reviewers', required: true }))
.toEqual({ key: 'next_reviewers', required: true });
expect(DecisionOutputDefSchema.parse({ key: 'note', required: false }))
Expand Down
20 changes: 10 additions & 10 deletions packages/spec/src/automation/builtin-node-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ function unknownKeyMessage(schema: Parseable, value: unknown): string | undefine
return result.error!.issues.find((i) => i.code === 'unrecognized_keys')?.message;
}

describe('CRUD config contracts — strict as of #4001 批 9', () => {
describe('CRUD config contracts — an unknown key is refused, not stripped', () => {
it('accepts every declared key on each of the four', () => {
expect(GetRecordConfigSchema.parse({
objectName: 'lead', filter: { status: 'new' }, fields: ['id'], limit: 5, outputVariable: 'leads',
Expand Down Expand Up @@ -85,7 +85,7 @@ describe('CRUD config contracts — strict as of #4001 批 9', () => {
['update_record', UpdateRecordConfigSchema, { objectName: 'lead' }],
['delete_record', DeleteRecordConfigSchema, { objectName: 'lead' }],
] as ReadonlyArray<[string, Parseable, Record<string, unknown>]>)(
'%s: prescribes `filter` for the retired `filters` spelling, and names the #3810 hazard',
'%s: prescribes `filter` for the retired `filters` spelling, and names the match-everything-write hazard',
(_nodeType, schema, base) => {
const message = unknownKeyMessage(schema, { ...base, filters: { status: 'stale' } })!;
expect(message).toContain('flow-node-crud-filter-alias');
Expand Down Expand Up @@ -204,7 +204,7 @@ describe('CRUD config contracts — strict as of #4001 批 9', () => {
});
});

describe('ScreenConfigSchema / ScreenFieldConfigSchema — strict as of #4001 批 9', () => {
describe('ScreenConfigSchema / ScreenFieldConfigSchema — an unknown key is refused, not stripped', () => {
it('accepts the flat and object-form shapes in full', () => {
expect(ScreenConfigSchema.safeParse({
title: 'Details', description: 'Fill this in', waitForInput: true,
Expand Down Expand Up @@ -249,7 +249,7 @@ describe('ScreenConfigSchema / ScreenFieldConfigSchema — strict as of #4001
});
});

describe('ScreenFieldConfigSchema — the bound pair, help text and lookup target (#17306)', () => {
describe('ScreenFieldConfigSchema — the bound pair, help text and lookup target', () => {
const BASE = { name: 'discount', type: 'number' };

// ── Direction 1: the three intents are now expressible ──────────────────
Expand Down Expand Up @@ -397,7 +397,7 @@ describe('ScreenFieldConfigSchema — the bound pair, help text and lookup targe
});
});

describe('MapConfigSchema — strict as of #4001 批 9', () => {
describe('MapConfigSchema — an unknown key is refused, not stripped', () => {
it('accepts every declared key', () => {
expect(MapConfigSchema.parse({
collection: '{tasks}', flowName: 'one_task_signoff', iteratorVariable: 'item',
Expand Down Expand Up @@ -430,7 +430,7 @@ describe('MapConfigSchema — strict as of #4001 批 9', () => {

// ─── assignment (#14149) ─────────────────────────────────────────────

describe('assignment value contract — a CEL envelope beside `{token}` interpolation (#14149)', () => {
describe('assignment value contract — a CEL envelope beside `{token}` interpolation', () => {
const DIGEST_SOURCE = 'joinNonEmpty(overdue_tasks.map(t, t.subject), "\\n")';
const DIGEST_ENVELOPE = { dialect: 'cel', source: DIGEST_SOURCE };

Expand Down Expand Up @@ -568,7 +568,7 @@ describe('assignment value contract — a CEL envelope beside `{token}` interpol
* registered, and faulted at run time (the `ast`-only one with the engine's
* own prescription; the blank one with a parse error on `" "`).
*/
describe('assignment value envelope — an evaluated slot requires what the engine can evaluate (#15430)', () => {
describe('assignment value envelope — an evaluated slot requires what the engine can evaluate', () => {
const AST_ONLY = { dialect: 'cel', ast: { kind: 'const', value: 1 } };
const BLANK_SOURCE = { dialect: 'cel', source: ' ' };

Expand Down Expand Up @@ -627,7 +627,7 @@ describe('assignment value envelope — an evaluated slot requires what the engi
* same structural reason as the sibling slot: `z.record()`'s open-key branch
* skips it before any key schema — including `.min(1)` — ever runs.
*/
describe('AssignmentConfigSchema.assignments — __proto__ pre-parse guard, constructor/prototype UNCHANGED (#17852 / #18847)', () => {
describe('AssignmentConfigSchema.assignments — __proto__ pre-parse guard, constructor/prototype UNCHANGED', () => {
it('refuses `assignments` carrying a `__proto__` own key, named at `assignments.__proto__`', () => {
// `JSON.parse` is what makes `__proto__` an OWN enumerable key — an
// object literal's `{ __proto__: ... }` sets the actual prototype
Expand Down Expand Up @@ -688,7 +688,7 @@ describe('AssignmentConfigSchema.assignments — __proto__ pre-parse guard, cons
* `__proto__` must be refused LOUDLY — told apart here from the two silent
* outcomes it could otherwise have (silently dropped, silently kept).
*/
describe('AssignmentConfigSchema — top-level __proto__ refused at the catchall (#19151)', () => {
describe('AssignmentConfigSchema — top-level __proto__ refused at the catchall', () => {
/**
* The three outcomes an authored key can meet, discriminated by one
* function so a test cannot accidentally assert the wrong one. A bare
Expand Down Expand Up @@ -807,7 +807,7 @@ describe('AssignmentConfigSchema — top-level __proto__ refused at the catchall
* CEL value envelope), the edge #14149 accepted on `assignments.*`. Everything
* else parses exactly as before.
*/
describe('CRUD `fields` value contract — the CEL value envelope beside `{token}` templates (#19938)', () => {
describe('CRUD `fields` value contract — the CEL value envelope beside `{token}` templates', () => {
const PRICE_ENVELOPE = { dialect: 'cel', source: 'round(price * 100) / 100.0' };
const configs = [
['create_record', CreateRecordConfigSchema, (fields: unknown) => ({ objectName: 'quote', fields })],
Expand Down
12 changes: 6 additions & 6 deletions packages/spec/src/automation/control-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ describe('LoopConfigSchema', () => {
).toThrow();
});

it('emits the xExpression:"template" marker on `collection` through z.toJSONSchema (objectui #2670)', () => {
it('emits the xExpression:"template" marker on `collection` through z.toJSONSchema, so the flow designer renders it as a template', () => {
// The marker rides the same `.meta()` → JSON-Schema channel as
// `xRef` / `xEnumDeprecated`, telling the flow designer `collection` is an
// `interpolate()` `{var}` template (not bare CEL).
Expand All @@ -77,7 +77,7 @@ describe('LoopConfigSchema', () => {
);
});

it('accepts an inline array collection — the union map.collection declares (#4277)', () => {
it('accepts an inline array collection — the union map.collection declares', () => {
// The executor has always resolved an already-an-array collection (shared
// logic with `map`); the string-only declaration under-declared what it
// reads, which the execute-time parse wiring surfaced.
Expand Down Expand Up @@ -272,7 +272,7 @@ describe('validateControlFlow', () => {

// ─── [#4001 批 10] unknown keys are rejected, not stripped ──────────────────

describe('[#4001] control-flow strictness — per shape', () => {
describe('control-flow strictness — an unknown key is refused, per shape', () => {
it('FlowRegion: `name` and `label` get wrong-layer prescriptions, not renames', () => {
for (const [key, expected] of [
['name', 'A region is not named'],
Expand Down Expand Up @@ -438,7 +438,7 @@ describe('TryCatchErrorValueSchema', () => {
expect(TryCatchErrorValueSchema.parse(both)).toEqual(both);
});

it('`code` is an OPEN string, not a closed enum — a third-party or tenant-authored code parses (ADR-0112 D3/D4 + #9106)', () => {
it('`code` is an OPEN string, not a closed enum — a third-party or tenant-authored code parses (ADR-0112 D3/D4)', () => {
// `NodeExecutor` is third-party-registrable and the code vocabulary is
// `StandardErrorCode` ∪ registered ledger codes ∪ tenant-authored codes,
// so a closed type would be false the moment anyone registers an executor.
Expand Down Expand Up @@ -491,7 +491,7 @@ describe('TryCatchErrorValueSchema', () => {
// `MAX_REGION_DEPTH` (32) `analyzeRegion`'s own `duplicate node id` line is
// the only refusal of a within-region duplicate. `flow.test.ts`'s
// `the seam at MAX_REGION_DEPTH` case pins that hand-off.
describe('[#4001] validateControlFlow and the key gate meet at the region-slot seam', () => {
describe('validateControlFlow and the unknown-key gate meet at the region-slot seam', () => {
const flowWith = (cfg: Record<string, unknown>, type = LOOP_NODE_TYPE) =>
({ nodes: [{ ...node('c1', type), config: cfg }] } as never);

Expand Down Expand Up @@ -540,7 +540,7 @@ describe('[#4001] validateControlFlow and the key gate meet at the region-slot s
expect(() => validateControlFlow(flowWith({ collection: '{items}', iteratorVariable: 'x' }))).not.toThrow();
});

it('nested regions are key-checked at depth, like the structural check (#4389)', () => {
it('nested regions are key-checked at depth, like the structural check', () => {
let message = '';
try {
validateControlFlow(flowWith({
Expand Down
8 changes: 4 additions & 4 deletions packages/spec/src/automation/end-node-outcome.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const flowEndingWith = (config: Record<string, unknown> | undefined): Flow => ({

const endConfigOf = (flow: { nodes: Array<{ config?: unknown }> }) => flow.nodes[1].config;

describe('EndConfigSchema — the `end` node contract (#14945)', () => {
describe('EndConfigSchema — the `end` node contract: it may refuse the run with a message', () => {
it('defaults `outcome` to `completed` on an empty config', () => {
expect(EndConfigSchema.parse({})).toEqual({ outcome: 'completed' });
expect(EndConfigSchema.parse({ outcome: 'completed' })).toEqual({ outcome: 'completed' });
Expand Down Expand Up @@ -127,7 +127,7 @@ describe('EndConfigSchema — the `end` node contract (#14945)', () => {
});
});

describe('FlowSchema applies the `end` contract — the structural node\'s only door (#14945)', () => {
describe('FlowSchema applies the `end` contract — the structural node\'s only door', () => {
it('accepts the card-shape probe and writes the parsed config back', () => {
const result = FlowSchema.safeParse(flowEndingWith({ outcome: 'refused', message: REFUSAL }));
expect(result.success).toBe(true);
Expand Down Expand Up @@ -217,7 +217,7 @@ describe('FlowSchema applies the `end` contract — the structural node\'s only
expect(issues?.map((i) => [i.code, i.path])).toEqual([['custom', ['nodes', 1, 'config', 'message']]]);
});

it('a region-nested `end` is refused by the FLOW parse itself (#15646/#18112) — the region-door reading this test used to pin is unreachable, because the shape is gone', () => {
it('a region-nested `end` is refused by the FLOW parse itself — the region-door reading this test used to pin is unreachable, because the shape is gone', () => {
// ⚠️ REPLACED, not re-spelled. This case used to assert that the flow parse
// was GREEN here and that `validateControlFlow` was the door — a true
// reading of `parseFlowNodeRegions` leaving a refused region raw (#4389).
Expand Down Expand Up @@ -278,7 +278,7 @@ describe('FlowSchema applies the `end` contract — the structural node\'s only
});
});

describe('the run row carries the refusal (#14945)', () => {
describe('the run row carries the refusal', () => {
const run = {
id: 'exec_refused_001',
flowName: 'lead_conversion',
Expand Down
10 changes: 5 additions & 5 deletions packages/spec/src/automation/execution.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('ExecutionStatus', () => {
});
});

it('names the refused terminal (#14945) — appended LAST, so every `.options` index reader keeps its positions', () => {
it('names the refused terminal — appended LAST, so every `.options` index reader keeps its positions', () => {
expect(ExecutionStatus.options).toContain('refused');
expect(ExecutionStatus.options.at(-1)).toBe('refused');
expect(ExecutionStatus.options.slice(0, 8)).toEqual(
Expand Down Expand Up @@ -204,7 +204,7 @@ describe('ExecutionStepLogSchema', () => {
expect(step.branch).toBe(1);
});

it('a `success` step that delegated to a child carries the child\'s contained failures on `metrics.failures` (#15617)', () => {
it('a `success` step that delegated to a child carries the child\'s contained failures on `metrics.failures`', () => {
// The card's shape: `loop { subflow(child) }`, one iteration whose child
// COMPLETED while losing a row. The subflow step itself succeeded — the
// failure is the child's, contained — so `status` stays `success` and the
Expand Down Expand Up @@ -415,7 +415,7 @@ describe('FlowRunSummarySchema', () => {
expect(summary.failed).toBe(summary.nodes.reduce((sum, node) => sum + node.failures, 0));
});

it('`failed` is the fold INCLUDING what a delegating node rolled up from its child — the card\'s measured shape, as ruled (#15617)', () => {
it('`failed` is the fold INCLUDING what a delegating node rolled up from its child — it answers what the run caused', () => {
// Parent `loop { subflow(child) }` over five rows; the child COMPLETED on
// every iteration and contained one failure on the last. The subflow node
// succeeded five times — `status: success`, its own executions never
Expand All @@ -440,7 +440,7 @@ describe('FlowRunSummarySchema', () => {
expect(call?.failures).toBe(1);
});

it('the control keeps counting as before: a child that FAILED is the delegating step\'s own failure, counted once (#15617)', () => {
it('the control keeps counting as before: a child that FAILED is the delegating step\'s own failure, counted once', () => {
// Same parent, but the child FAILED on the last row rather than containing
// the failure. That is the subflow step's own `status: failure` — one
// execution failed — and nothing rides up on top of it: the child's own
Expand All @@ -457,7 +457,7 @@ describe('FlowRunSummarySchema', () => {
expect(summary.failed).toBe(summary.nodes.reduce((sum, node) => sum + node.failures, 0));
});

it('declares the roll-up at the point of use — the field describes say so, not only the paragraph above the schema (#15617)', () => {
it('declares the roll-up at the point of use — the field describes say so, not only the paragraph above the schema', () => {
// Triage's explicit failure mode for this card: a reconciliation that
// leaves `failed`'s own `.describe()` saying the narrow thing. A consumer
// reads the field's description, so the widened rule has to be there.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,14 +60,14 @@ const AT_BRANCH_0 = ['nodes', 1, 'config', 'conditions', 0, 'expression'];
* refused row's message is the ONE judge's, byte for byte.
*/
const TABLE: Array<{ name: string; branch: Node; refused: boolean; refusedWith?: unknown }> = [
{ name: 'no `expression` key — the #19961 shape', branch: { label: 'y' }, refused: true, refusedWith: undefined },
{ name: 'no `expression` key', branch: { label: 'y' }, refused: true, refusedWith: undefined },
{ name: '`expression: null`', branch: { label: 'y', expression: null }, refused: true, refusedWith: null },
{ name: 'the predicate under the edge\'s spelling `condition`', branch: { label: 'y', condition: 'true' }, refused: true, refusedWith: undefined },
{ name: 'a blank string — the #17493 control', branch: { label: 'y', expression: ' ' }, refused: true, refusedWith: ' ' },
{ name: 'a blank string — blanks are refused', branch: { label: 'y', expression: ' ' }, refused: true, refusedWith: ' ' },
{ name: 'a real predicate — the accept control', branch: { label: 'y', expression: 'true' }, refused: false },
];

describe('FlowSchema.parse refuses a decision branch with no `expression` (#19961)', () => {
describe('FlowSchema.parse refuses a decision branch with no `expression`', () => {
it.each(TABLE)('$name', ({ branch, refused, refusedWith }) => {
const issues = issuesOf(flowWith(decision(branch)));
if (!refused) {
Expand Down
Loading
Loading