/** * Adversarial case corpus for Phase 4D parser conformance. * * Each case is raw bytes as an author could actually produce them. Nothing here * is normalized on the way in: the point is what the two parsers do with the * awkward form, so the awkward form is what is stored. */ const SKILL = [ '---', 'id: sample-skill', 'name: Sample Skill', 'description: A sample skill.', 'pages:', ' - candidates', 'status: active', 'actions:', ' - navigate_to_candidates', '---', '', '# Sample Skill', '', '## Purpose', '', '- Read the pipeline.', '', '## Capabilities', '', '- Summarize candidates.', '', ].join('\n'); const AGENT = [ '---', 'id: sample-agent', 'name: Sample Agent', 'description: A sample agent.', 'icon: users', 'status: published', 'version: 2', 'reasoning: balanced', 'trigger: Use on candidates.', 'pages:', ' - candidates', 'skills:', ' - candidate-search', 'starters:', ' - label: Who is waiting?', ' prompt: Who is waiting on a decision?', 'permissions:', ' owner: demo@krow.app', ' access: all', '---', '', '# Sample Agent', '', '## Instructions', '', 'Answer about candidates.', '', '## Purpose', '', '- Report the pipeline.', '', ].join('\n'); /** A skill body, with the frontmatter lines replaced wholesale. */ const skillWith = (fmLines) => ['---', ...fmLines, '---', '', '# Sample Skill', '', '## Purpose', '', '- Read the pipeline.', ''].join('\n'); const agentWith = (fmLines) => ['---', ...fmLines, '---', '', '# Sample Agent', '', '## Instructions', '', 'Answer.', ''].join('\n'); const BASE_SKILL_FM = [ 'id: sample-skill', 'name: Sample Skill', 'description: A sample skill.', 'pages:', ' - candidates', ]; const BASE_AGENT_FM = [ 'id: sample-agent', 'name: Sample Agent', 'description: A sample agent.', 'pages:', ' - candidates', ]; /** BASE_SKILL_FM with one line appended. */ const skillPlus = (...extra) => skillWith([...BASE_SKILL_FM, ...extra]); const agentPlus = (...extra) => agentWith([...BASE_AGENT_FM, ...extra]); export const CASES = [ /* ── Baselines ────────────────────────────────────────────────────────── */ { name: 'baseline-skill', kind: 'skill', raw: SKILL }, { name: 'baseline-agent', kind: 'agent', raw: AGENT }, /* ── Byte-level shape ─────────────────────────────────────────────────── */ { name: 'utf8-bom', kind: 'skill', raw: '' + SKILL }, { name: 'utf8-bom-agent', kind: 'agent', raw: '' + AGENT }, { name: 'crlf', kind: 'skill', raw: SKILL.replace(/\n/g, '\r\n') }, { name: 'crlf-agent', kind: 'agent', raw: AGENT.replace(/\n/g, '\r\n') }, { name: 'cr-only', kind: 'skill', raw: SKILL.replace(/\n/g, '\r') }, { name: 'bom-crlf-blankline', kind: 'skill', raw: '\r\n' + SKILL.replace(/\n/g, '\r\n') }, { name: 'leading-blank-line', kind: 'skill', raw: '\n' + SKILL }, { name: 'multiple-leading-blank-lines', kind: 'skill', raw: '\n\n\n' + SKILL }, { name: 'leading-spaces-then-blank-lines', kind: 'skill', raw: ' \n \n' + SKILL }, { name: 'no-trailing-newline', kind: 'skill', raw: SKILL.replace(/\n+$/, '') }, { name: 'many-trailing-newlines', kind: 'skill', raw: SKILL + '\n\n\n' }, { name: 'trailing-spaces-on-values', kind: 'skill', raw: skillWith(BASE_SKILL_FM.map((l) => l + ' ')) }, { name: 'trailing-ws-after-open-fence', kind: 'skill', raw: SKILL.replace(/^---/, '--- ') }, { name: 'trailing-tab-after-close-fence', kind: 'skill', raw: SKILL.replace(/\n---\n/, '\n---\t\n') }, { name: 'frontmatter-only-no-body', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n---' }, { name: 'frontmatter-only-trailing-newline', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n---\n' }, /* ── Scalars ──────────────────────────────────────────────────────────── */ { name: 'double-quoted-scalar', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: "Sample Skill"', 'description: "A sample."', 'pages:', ' - candidates']) }, { name: 'single-quoted-scalar', kind: 'skill', raw: skillWith(["id: sample-skill", "name: 'Sample Skill'", "description: 'A sample.'", 'pages:', ' - candidates']) }, { name: 'colon-in-quoted-string', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: "Sample: Skill"', 'description: "Note: read this."', 'pages:', ' - candidates']) }, { name: 'colon-in-unquoted-string', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: Note: read this.', 'pages:', ' - candidates']) }, { name: 'hash-in-quoted-string', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: "Shift #1"', 'description: "Tag #ops"', 'pages:', ' - candidates']) }, { name: 'hash-unquoted-trailing-comment', kind: 'skill', raw: skillPlus('category: ops # a trailing comment') }, { name: 'hash-unquoted-midword', kind: 'skill', raw: skillPlus('category: ops#1') }, { name: 'doubled-quote-escape', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: "She said ""go"""', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'full-line-comment', kind: 'skill', raw: skillWith(['# a comment line', ...BASE_SKILL_FM]) }, { name: 'empty-scalar', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description:', 'pages:', ' - candidates']) }, { name: 'tilde-scalar', kind: 'skill', raw: skillPlus('category: ~') }, { name: 'null-scalar', kind: 'skill', raw: skillPlus('category: null') }, { name: 'boolean-scalar', kind: 'agent', raw: agentPlus('webSearch: true') }, { name: 'integer-scalar', kind: 'agent', raw: agentPlus('version: 3') }, { name: 'float-scalar', kind: 'agent', raw: agentPlus('version: 1.5') }, { name: 'negative-integer', kind: 'agent', raw: agentPlus('version: -2') }, /* ── Collections ──────────────────────────────────────────────────────── */ { name: 'empty-array', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates', 'actions:']) }, { name: 'inline-flow-array', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages: [candidates]']) }, { name: 'inline-flow-map', kind: 'agent', raw: agentPlus('permissions: {owner: a, access: all}') }, { name: 'sequence-of-mappings', kind: 'agent', raw: AGENT }, { name: 'nested-mapping', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'permissions:', ' owner: demo@krow.app', ' access: specific', ' people:', ' - user: a@b.c', ' role: editor']) }, { name: 'dash-alone-nested-block', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'starters:', ' -', ' label: Hello', ' prompt: Hello there']) }, { name: 'tab-indented-sequence', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', '\t- candidates']) }, { name: 'four-space-indent', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'ragged-indent', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates', ' - positions']) }, /* ── Multiline / unsupported YAML ─────────────────────────────────────── */ { name: 'block-scalar-literal', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: |', ' Line one.', ' Line two.', 'pages:', ' - candidates']) }, { name: 'block-scalar-folded', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: >', ' Line one.', 'pages:', ' - candidates']) }, { name: 'anchor-and-alias', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: &n Sample Skill', 'description: *n', 'pages:', ' - candidates']) }, { name: 'multi-document', kind: 'skill', raw: skillWith([...BASE_SKILL_FM, '---', 'id: second']) }, /* ── Keys ─────────────────────────────────────────────────────────────── */ { name: 'duplicate-key', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: First Name', 'name: Second Name', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'duplicate-key-array', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates', 'pages:', ' - positions']) }, { name: 'unsupported-frontmatter-field', kind: 'skill', raw: skillPlus('unknownField: whatever') }, { name: 'unsupported-field-agent', kind: 'agent', raw: agentPlus('nonsense: 1') }, { name: 'key-with-space', kind: 'skill', raw: skillWith(['id: sample-skill', 'my key: value', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'uppercase-key', kind: 'skill', raw: skillPlus('Category: Ops') }, /* ── Malformed ────────────────────────────────────────────────────────── */ { name: 'malformed-yaml-bare-line', kind: 'skill', raw: skillWith(['id: sample-skill', 'this is not a pair', 'name: Sample Skill', 'pages:', ' - candidates']) }, { name: 'malformed-open-fence-two-dashes', kind: 'skill', raw: SKILL.replace(/^---/, '--') }, { name: 'malformed-open-fence-four-dashes', kind: 'skill', raw: SKILL.replace(/^---/, '----') }, { name: 'malformed-open-fence-indented', kind: 'skill', raw: ' ' + SKILL }, { name: 'malformed-open-fence-text-after', kind: 'skill', raw: SKILL.replace(/^---/, '---yaml') }, { name: 'malformed-close-fence-two-dashes', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n--\n\n# Body\n' }, { name: 'malformed-close-fence-missing', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n\n# Body\n' }, { name: 'malformed-close-fence-four-dashes', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n----\n\n# Body\n' }, { name: 'empty-fence-pair', kind: 'skill', raw: '---\n---\n\n# Body\n' }, { name: 'empty-fence-with-blank', kind: 'skill', raw: '---\n\n---\n\n# Body\n' }, { name: 'missing-frontmatter', kind: 'skill', raw: '# Sample Skill\n\n## Purpose\n\n- Read the pipeline.\n' }, { name: 'empty-markdown', kind: 'skill', raw: '' }, { name: 'whitespace-only-markdown', kind: 'skill', raw: ' \n\n\t\n' }, { name: 'frontmatter-is-a-sequence', kind: 'skill', raw: '---\n- one\n- two\n---\n\n# Body\n' }, /* ── Field-level validity ─────────────────────────────────────────────── */ { name: 'invalid-definition-id-uppercase', kind: 'skill', raw: skillWith(['id: Sample_Skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'invalid-definition-id-leading-dash', kind: 'skill', raw: skillWith(['id: -sample', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'invalid-definition-id-underscore', kind: 'skill', raw: skillWith(['id: sample_skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'missing-id-derives-from-name', kind: 'skill', raw: skillWith(['name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'missing-name', kind: 'skill', raw: skillWith(['id: sample-skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'missing-pages', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.']) }, { name: 'unknown-page', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - nowhere']) }, { name: 'page-alias-uppercase', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - Candidates']) }, { name: 'page-alias-underscore', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - talent_pool']) }, { name: 'invalid-status-skill', kind: 'skill', raw: skillPlus('status: bogus') }, { name: 'inactive-status-skill', kind: 'skill', raw: skillPlus('status: inactive') }, { name: 'invalid-status-agent', kind: 'agent', raw: agentPlus('status: bogus') }, { name: 'invalid-reasoning-agent', kind: 'agent', raw: agentPlus('reasoning: turbo') }, { name: 'invalid-icon-agent', kind: 'agent', raw: agentPlus('icon: rocket') }, { name: 'invalid-version-agent', kind: 'agent', raw: agentPlus('version: zero') }, { name: 'invalid-field-type-pages-scalar', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages: candidates']) }, { name: 'invalid-field-type-pages-scalar-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name: Sample Agent', 'description: A sample.', 'pages: candidates']) }, { name: 'invalid-field-type-name-list', kind: 'skill', raw: skillWith(['id: sample-skill', 'name:', ' - a', ' - b', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'invalid-permissions-access', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'permissions:', ' access: nobody']) }, { name: 'self-subagent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'subagents:', ' - sample-agent']) }, /* ── Size ─────────────────────────────────────────────────────────────── */ { name: 'oversized-markdown', kind: 'skill', raw: skillPlus() + '\n' + 'x'.repeat(65600) }, { name: 'at-size-bound', kind: 'skill', raw: (() => { const b = skillPlus(); return b + 'y'.repeat(65536 - b.length); })() }, /* ── Gaps closed after the first oracle run ───────────────────────────── */ { name: 'missing-name-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'missing-pages-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name: Sample Agent', 'description: A sample.']) }, { name: 'unknown-page-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name: Sample Agent', 'description: A sample.', 'pages:', ' - nowhere']) }, { name: 'oversized-agent', kind: 'agent', raw: agentPlus() + '\n' + 'x'.repeat(65600) }, { name: 'visibility-field-personal', kind: 'skill', raw: skillPlus('visibility: personal') }, { name: 'visibility-field-invalid', kind: 'skill', raw: skillPlus('visibility: nobody') }, { name: 'blank-page-entry-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name: Sample Agent', 'description: A sample.', 'pages:', ' - candidates', ' - ""']) }, { name: 'duplicate-page-entry-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name: Sample Agent', 'description: A sample.', 'pages:', ' - candidates', ' - candidates']) }, { name: 'id-with-trailing-space', kind: 'skill', raw: skillWith(['id: sample-skill ', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'id-quoted', kind: 'skill', raw: skillWith(['id: "sample-skill"', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'starters-plain-strings-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'starters:', ' - Who is waiting?', ' - Where are the gaps?']) }, { name: 'starter-missing-label-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'starters:', ' - prompt: Only a prompt']) }, { name: 'knowledge-note-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'knowledge:', ' - label: A note', ' body: The body of the note.']) }, { name: 'knowledge-bad-kind-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'knowledge:', ' - label: A note', ' kind: rumour', ' body: x']) }, { name: 'knowledge-link-without-url-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'knowledge:', ' - label: A link', ' kind: link']) }, { name: 'skills-scalar-coerced-agent', kind: 'agent', raw: agentPlus('skills: candidate-search') }, { name: 'skills-blank-entry-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'skills:', ' - candidate-search', ' - ""']) }, { name: 'web-search-snake-case-agent', kind: 'agent', raw: agentPlus('web_search: true') }, { name: 'web-search-string-true-agent', kind: 'agent', raw: agentPlus('webSearch: "true"') }, { name: 'version-quoted-integer-agent', kind: 'agent', raw: agentPlus('version: "3"') }, { name: 'version-zero-agent', kind: 'agent', raw: agentPlus('version: 0') }, { name: 'version-empty-agent', kind: 'agent', raw: agentPlus('version:') }, { name: 'crlf-inside-frontmatter-only', kind: 'skill', raw: (() => { const i = SKILL.indexOf('\n---\n', 3); return SKILL.slice(0, i).replace(/\n/g, '\r\n') + SKILL.slice(i); })() }, { name: 'body-whitespace-only-after-fence', kind: 'skill', raw: '---\n' + BASE_SKILL_FM.join('\n') + '\n---\n \n\t\n' }, { name: 'description-whitespace-only', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: " "', 'pages:', ' - candidates']) }, { name: 'permissions-null-agent', kind: 'agent', raw: agentPlus('permissions:') }, { name: 'permissions-people-bad-role-agent', kind: 'agent', raw: agentWith([...BASE_AGENT_FM, 'permissions:', ' access: specific', ' people:', ' - user: a@b.c', ' role: overlord']) }, /* ── Gaps closed after the second oracle run ────────────────────────────── * * Two rules had no case that could tell a correct implementation from a * plausible wrong one, so both were being asserted by accident: * * The id fallback chain. Every earlier case either declares `id:` or * declares a `name:` to slug, so the THIRD link — the default path, which * is the literal `custom` on both sides — was never reached by a case that * is otherwise valid. `id-omitted-unnamed` is that case: the frontend * accepts it as the skill `custom`, and a backend deriving no id would * refuse it on save. * * Text coercion. `name`, `description`, `category`, `trigger` and the * entries of `pages` are text columns in migration 000005, and a definition * may write any of them as a list, a number or a boolean. The frontend * keeps the raw value on the record and stringifies it at each use; the * backend has to choose the string at the boundary. These cases pin which * string it chooses. */ { name: 'id-omitted-unnamed', kind: 'skill', raw: skillWith(['description: A sample.', 'pages:', ' - candidates']) }, { name: 'id-omitted-named', kind: 'skill', raw: skillWith(['name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'id-omitted-unnamed-agent', kind: 'agent', raw: agentWith(['description: A sample.', 'pages:', ' - candidates']) }, { name: 'id-omitted-named-agent', kind: 'agent', raw: agentWith(['name: Sample Agent', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'id-omitted-name-unsluggable', kind: 'skill', raw: skillWith(['name: "!!!"', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'name-numeric', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: 42', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'name-boolean', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: true', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'description-list', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description:', ' - one', ' - two', 'pages:', ' - candidates']) }, { name: 'description-numeric', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: 7', 'pages:', ' - candidates']) }, { name: 'pages-numeric-entry', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - candidates', ' - 5']) }, { name: 'pages-mapping-entry', kind: 'skill', raw: skillWith(['id: sample-skill', 'name: Sample Skill', 'description: A sample.', 'pages:', ' - page: candidates']) }, { name: 'trigger-list-agent', kind: 'agent', raw: agentPlus('trigger:', ' - one', ' - two') }, { name: 'name-list-agent', kind: 'agent', raw: agentWith(['id: sample-agent', 'name:', ' - a', ' - b', 'description: A sample.', 'pages:', ' - candidates']) }, { name: 'category-numeric', kind: 'skill', raw: skillPlus('category: 3') }, /* The other backend-only bound. agent_definitions.version is a PostgreSQL `integer`; the frontend accepts any whole number of 1 or more, so a version above 2^31-1 is one the editor takes and the database cannot store. It had no case at all, which left the rule asserted by nobody. */ { name: 'version-above-int32-agent', kind: 'agent', raw: agentPlus('version: 3000000000') }, { name: 'version-at-int32-agent', kind: 'agent', raw: agentPlus('version: 2147483647') }, ];