From a502138e54f4120a658b2e819fe3472a69f3e2e1 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 13 Jul 2026 14:23:22 -0400 Subject: [PATCH 1/3] Run Docs Agent through the native executor --- .github/workflows/ci.yml | 38 +++++++++ .github/workflows/docs-agent.yml | 69 +++++----------- composer.json | 1 + tests/docs-agent-native-workflow-contract.php | 81 +++++++++++++++++++ 4 files changed, 141 insertions(+), 48 deletions(-) create mode 100644 tests/docs-agent-native-workflow-contract.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f07741..31a71cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,3 +41,41 @@ jobs: - name: Run ${{ matrix.label }} run: composer ${{ matrix.command }} + + workflow-contracts: + name: Workflow contracts + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6.0.3 + + - name: Checkout Docs Agent producer + uses: actions/checkout@v6.0.3 + with: + repository: Automattic/docs-agent + ref: main + path: .docs-agent-producer + + - name: Checkout WP Codebox producer schema + uses: actions/checkout@v6.0.3 + with: + repository: Automattic/wp-codebox + ref: 54c2f9a7bc3cd1fe20055d496c83efcfb99afb41 + path: .wp-codebox-producer + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + coverage: none + + - name: Validate native Docs Agent producer contract + env: + DOCS_AGENT_DIR: ${{ github.workspace }}/.docs-agent-producer + WP_CODEBOX_DIR: ${{ github.workspace }}/.wp-codebox-producer + run: php tests/docs-agent-native-workflow-contract.php + + - name: Lint GitHub Actions workflows + uses: docker://rhysd/actionlint:1.7.12 + with: + args: .github/workflows/*.yml diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml index 5a4d56a..c960b36 100644 --- a/.github/workflows/docs-agent.yml +++ b/.github/workflows/docs-agent.yml @@ -4,72 +4,45 @@ on: workflow_dispatch: inputs: prompt: - description: Optional additional instruction for Docs Agent. + description: Optional instruction appended to the technical documentation maintenance task. required: false - default: Generate or update Agents API documentation from repository source. Open a documentation pull request only when changes are needed. - audience: - description: Documentation lane to maintain. - type: choice - required: true - options: - - technical - - user - default: technical - run_kind: - description: Docs Agent run kind. - type: choice - required: true - options: - - maintenance - - bootstrap - default: maintenance - openai_model: - description: OpenAI model to use for Docs Agent. - required: true - default: gpt-5.5 - docs_agent_ref: - description: Automattic/docs-agent ref to use for the Docs Agent recipe. - required: true - default: main - base_ref: - description: Base branch or ref for documentation pull requests. - required: true - default: main - docs_branch: - description: Stable branch reused for the Docs Agent pull request. - required: true - default: docs-agent/docs-upkeep - writable_paths: - description: Comma-separated paths Docs Agent may edit. - required: true - default: README.md,docs/** + default: Generate or update Agents API developer documentation from repository source. Open a documentation pull request only when changes are needed. permissions: contents: write pull-requests: write issues: write +concurrency: + group: docs-agent-${{ github.repository }} + cancel-in-progress: false + jobs: docs-agent: - name: Maintain Documentation + name: Maintain Technical Documentation uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@main with: - audience: ${{ inputs.audience }} - run_kind: ${{ inputs.run_kind }} - base_ref: ${{ inputs.base_ref }} - docs_branch: ${{ inputs.docs_branch }} - writable_paths: ${{ inputs.writable_paths }} - model: ${{ inputs.openai_model }} - docs_agent_ref: ${{ inputs.docs_agent_ref }} + audience: technical + run_kind: maintenance + base_ref: main + docs_branch: docs-agent/agents-api-docs-upkeep + writable_paths: README.md,docs/** verification_commands: | [ + "composer test", "php tests/no-product-imports-smoke.php" ] + drift_checks: | + [ + "git diff --check" + ] prompt: | ${{ inputs.prompt }} Target repository: Automattic/agents-api Workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - Preserve the Agents API product-neutral boundary. Use Agents API as the product name in production and package documentation. Keep generated documentation focused on the public API substrate, repository source, developer contracts, setup, tests, and contributor workflows for the selected audience. - secrets: inherit + Maintain source-grounded developer documentation for the public, product-neutral Agents API substrate. Cover developer contracts, setup, tests, and contributor workflows using repository source as evidence. + secrets: + ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + EXTERNAL_PACKAGE_SOURCE_POLICY: ${{ secrets.EXTERNAL_PACKAGE_SOURCE_POLICY }} diff --git a/composer.json b/composer.json index 4fb1c37..cba4af5 100644 --- a/composer.json +++ b/composer.json @@ -132,6 +132,7 @@ "php tests/event-trigger-smoke.php", "php tests/subagents-smoke.php", "php tests/access-decision-filter-smoke.php", + "php tests/docs-agent-native-workflow-contract.php", "php tests/no-product-imports-smoke.php" ], "test": [ diff --git a/tests/docs-agent-native-workflow-contract.php b/tests/docs-agent-native-workflow-contract.php new file mode 100644 index 0000000..f550d51 --- /dev/null +++ b/tests/docs-agent-native-workflow-contract.php @@ -0,0 +1,81 @@ + Date: Mon, 13 Jul 2026 14:47:00 -0400 Subject: [PATCH 2/3] Fix Docs Agent native workflow contract --- .github/workflows/ci.yml | 5 +- .github/workflows/docs-agent.yml | 4 +- tests/docs-agent-native-workflow-contract.php | 141 ++++++++++++------ 3 files changed, 100 insertions(+), 50 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31a71cf..a6aa007 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,7 @@ jobs: uses: actions/checkout@v6.0.3 with: repository: Automattic/docs-agent - ref: main + ref: 5344a4bfbda4a0553cc92636258e46a715b1c72d path: .docs-agent-producer - name: Checkout WP Codebox producer schema @@ -78,4 +78,5 @@ jobs: - name: Lint GitHub Actions workflows uses: docker://rhysd/actionlint:1.7.12 with: - args: .github/workflows/*.yml + # With no paths, actionlint discovers and reads every workflow file. + args: -no-color diff --git a/.github/workflows/docs-agent.yml b/.github/workflows/docs-agent.yml index c960b36..9407675 100644 --- a/.github/workflows/docs-agent.yml +++ b/.github/workflows/docs-agent.yml @@ -20,7 +20,7 @@ concurrency: jobs: docs-agent: name: Maintain Technical Documentation - uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@main + uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@5344a4bfbda4a0553cc92636258e46a715b1c72d with: audience: technical run_kind: maintenance @@ -44,5 +44,5 @@ jobs: Maintain source-grounded developer documentation for the public, product-neutral Agents API substrate. Cover developer contracts, setup, tests, and contributor workflows using repository source as evidence. secrets: - ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} EXTERNAL_PACKAGE_SOURCE_POLICY: ${{ secrets.EXTERNAL_PACKAGE_SOURCE_POLICY }} diff --git a/tests/docs-agent-native-workflow-contract.php b/tests/docs-agent-native-workflow-contract.php index f550d51..e86d7c5 100644 --- a/tests/docs-agent-native-workflow-contract.php +++ b/tests/docs-agent-native-workflow-contract.php @@ -1,18 +1,22 @@ /dev/null' ) ); + agents_api_docs_agent_contract_assert( $expected === $revision, "{$name} must be checked out at {$expected}; found {$revision}.", $failures ); +} -agents_api_docs_agent_contract_assert( - ( is_string( $docs_agent_dir ) && '' !== $docs_agent_dir ) === ( is_string( $wp_codebox_dir ) && '' !== $wp_codebox_dir ), - 'DOCS_AGENT_DIR and WP_CODEBOX_DIR must be configured together.', - $failures -); - -if ( is_string( $docs_agent_dir ) && '' !== $docs_agent_dir && is_string( $wp_codebox_dir ) && '' !== $wp_codebox_dir ) { - $docs_workflow = agents_api_docs_agent_contract_read( $docs_agent_dir, '.github/workflows/maintain-docs.yml', $failures ); - $wp_codebox_schema = agents_api_docs_agent_contract_read( $wp_codebox_dir, 'contracts/run-agent-task-reusable-workflow-interface.v1.json', $failures ); - $wp_codebox_schema = json_decode( $wp_codebox_schema, true ); - - agents_api_docs_agent_contract_assert( is_array( $wp_codebox_schema ), 'WP Codebox producer schema must be valid JSON.', $failures ); - agents_api_docs_agent_contract_assert( 'wp-codebox/reusable-workflow-interface/v1' === ( $wp_codebox_schema['schema'] ?? null ), 'WP Codebox producer schema version must match.', $failures ); - agents_api_docs_agent_contract_assert( isset( $wp_codebox_schema['inputs']['external_package_source'] ), 'WP Codebox must require an external native package source.', $failures ); - agents_api_docs_agent_contract_assert( isset( $wp_codebox_schema['secrets']['EXTERNAL_PACKAGE_SOURCE_POLICY'] ), 'WP Codebox must require the external package source policy.', $failures ); - agents_api_docs_agent_contract_assert( str_contains( $docs_workflow, 'technical-docs-maintenance-agent.agent.json' ), 'Docs Agent must map technical maintenance to its native package.', $failures ); - agents_api_docs_agent_contract_assert( str_contains( $docs_workflow, 'external_package_source:' ), 'Docs Agent must pass a native external package source to its runner.', $failures ); - agents_api_docs_agent_contract_assert( str_contains( $docs_workflow, 'EXTERNAL_PACKAGE_SOURCE_POLICY: ${{ secrets.EXTERNAL_PACKAGE_SOURCE_POLICY }}' ), 'Docs Agent must forward the external package source policy.', $failures ); - agents_api_docs_agent_contract_assert( str_contains( $docs_workflow, 'ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}' ), 'Docs Agent must forward the publication token.', $failures ); +function agents_api_docs_agent_contract_match( string $content, string $pattern, string $message, array &$failures ): void { + agents_api_docs_agent_contract_assert( 1 === preg_match( $pattern, $content ), $message, $failures ); } -foreach ( - array( - 'uses: Automattic/docs-agent/.github/workflows/maintain-docs.yml@main', - 'audience: technical', - 'run_kind: maintenance', - 'base_ref: main', - 'docs_branch: docs-agent/agents-api-docs-upkeep', - 'writable_paths: README.md,docs/**', - '"composer test"', - '"php tests/no-product-imports-smoke.php"', - '"git diff --check"', - 'ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}', - 'EXTERNAL_PACKAGE_SOURCE_POLICY: ${{ secrets.EXTERNAL_PACKAGE_SOURCE_POLICY }}', - ) as $required_fragment ) { - agents_api_docs_agent_contract_assert( str_contains( $consumer_workflow, $required_fragment ), "Docs Agent consumer must include {$required_fragment}.", $failures ); +$docs_agent_dir = agents_api_docs_agent_contract_directory( 'DOCS_AGENT_DIR', $failures ); +$wp_codebox_dir = agents_api_docs_agent_contract_directory( 'WP_CODEBOX_DIR', $failures ); + +if ( $failures ) { + fwrite( STDERR, "Docs Agent native workflow contract failed:\n- " . implode( "\n- ", $failures ) . "\n" ); + exit( 1 ); } -foreach ( array( 'openai_model:', 'docs_agent_ref:', 'run_agent:', 'agent_bundle:', 'validation_dependencies:', 'context_repositories:', 'bootstrap_contract:' ) as $obsolete_fragment ) { - agents_api_docs_agent_contract_assert( ! str_contains( $consumer_workflow, $obsolete_fragment ), "Docs Agent consumer must not retain obsolete input {$obsolete_fragment}.", $failures ); +agents_api_docs_agent_contract_revision( $docs_agent_dir, AGENTS_API_DOCS_AGENT_REVISION, 'Docs Agent', $failures ); +agents_api_docs_agent_contract_revision( $wp_codebox_dir, AGENTS_API_WP_CODEBOX_REVISION, 'WP Codebox', $failures ); + +$consumer_workflow = (string) file_get_contents( $root . '/.github/workflows/docs-agent.yml' ); +$docs_workflow = agents_api_docs_agent_contract_read( $docs_agent_dir, '.github/workflows/maintain-docs.yml', $failures ); +$schema_json = agents_api_docs_agent_contract_read( $wp_codebox_dir, 'contracts/run-agent-task-reusable-workflow-interface.v1.json', $failures ); +$schema = json_decode( $schema_json, true ); + +agents_api_docs_agent_contract_assert( is_array( $schema ), 'WP Codebox reusable-workflow schema must be valid JSON.', $failures ); +if ( is_array( $schema ) ) { + agents_api_docs_agent_contract_assert( 'wp-codebox/reusable-workflow-interface/v1' === ( $schema['schema'] ?? null ), 'WP Codebox reusable-workflow schema version must match.', $failures ); + + foreach ( + array( + 'external_package_source' => array( true, 'string' ), + 'target_repo' => array( true, 'string' ), + 'prompt' => array( true, 'string' ), + 'writable_paths' => array( false, 'string' ), + 'verification_commands' => array( false, 'string' ), + 'drift_checks' => array( false, 'string' ), + 'success_requires_pr' => array( false, 'boolean' ), + 'access_token_repos' => array( false, 'string' ), + 'require_access_token' => array( false, 'boolean' ), + 'allowed_repos' => array( false, 'string' ), + 'run_agent' => array( false, 'boolean' ), + 'dry_run' => array( false, 'boolean' ), + ) as $input => $contract + ) { + agents_api_docs_agent_contract_assert( $contract[0] === ( $schema['inputs'][ $input ]['required'] ?? null ), "WP Codebox {$input} required contract must match.", $failures ); + agents_api_docs_agent_contract_assert( $contract[1] === ( $schema['inputs'][ $input ]['type'] ?? null ), "WP Codebox {$input} type contract must match.", $failures ); + } + + foreach ( array( 'OPENAI_API_KEY' => false, 'ACCESS_TOKEN' => false, 'EXTERNAL_PACKAGE_SOURCE_POLICY' => true ) as $secret => $required ) { + agents_api_docs_agent_contract_assert( $required === ( $schema['secrets'][ $secret ]['required'] ?? null ), "WP Codebox {$secret} secret contract must match.", $failures ); + } } +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*uses:\s*Automattic/docs-agent/\.github/workflows/maintain-docs\.yml@' . AGENTS_API_DOCS_AGENT_REVISION . '\s*$~m', 'Consumer must pin Docs Agent #121.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*audience:\s*technical\s*$~m', 'Consumer audience must be technical.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*run_kind:\s*maintenance\s*$~m', 'Consumer run kind must be maintenance.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*base_ref:\s*main\s*$~m', 'Consumer target base must be main.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*docs_branch:\s*docs-agent/agents-api-docs-upkeep\s*$~m', 'Consumer publication branch must be stable and exact.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*writable_paths:\s*README\.md,docs/\*\*\s*$~m', 'Consumer writable paths must be README.md,docs/**.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~"composer test"\s*,\s*"php tests/no-product-imports-smoke\.php"~s', 'Consumer verification commands must include the test chain.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~"git diff --check"~', 'Consumer drift check must run git diff --check.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*OPENAI_API_KEY:\s*\$\{\{ secrets\.OPENAI_API_KEY \}\}\s*$~m', 'Consumer must forward OPENAI_API_KEY.', $failures ); +agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*EXTERNAL_PACKAGE_SOURCE_POLICY:\s*\$\{\{ secrets\.EXTERNAL_PACKAGE_SOURCE_POLICY \}\}\s*$~m', 'Consumer must forward EXTERNAL_PACKAGE_SOURCE_POLICY.', $failures ); +agents_api_docs_agent_contract_assert( 0 === preg_match( '~secrets\.ACCESS_TOKEN|^\s*ACCESS_TOKEN:\s*~m', $consumer_workflow ), 'Consumer must not require or forward ACCESS_TOKEN.', $failures ); + +foreach ( array( 'contents: write', 'pull-requests: write', 'issues: write' ) as $permission ) { + agents_api_docs_agent_contract_match( $consumer_workflow, '~^\s*' . preg_quote( $permission, '~' ) . '\s*$~m', "Consumer must grant {$permission} for built-in-token publication.", $failures ); +} + +agents_api_docs_agent_contract_match( $docs_workflow, '~technical:maintenance\)\s+package_path="bundles/technical-docs-agent/native/technical-docs-maintenance-agent\.agent\.json"\s+agent_slug="technical-docs-maintenance-agent"\s+package_digest="sha256-bytes-v1:6057aad4eb7c5f0320ccfbce9da93a5fa1d3fc521478b5571ed81c28129325aa"\s+success_requires_pr=false~s', 'Docs Agent #121 technical maintenance lane must map to its exact native package.', $failures ); +agents_api_docs_agent_contract_match( $docs_workflow, '~uses:\s*Automattic/wp-codebox/\.github/workflows/run-agent-task\.yml@' . AGENTS_API_WP_CODEBOX_REVISION . '~', 'Docs Agent must pin WP Codebox #1754.', $failures ); +agents_api_docs_agent_contract_match( $docs_workflow, '~external_package_source:\s*\$\{\{ needs\.prepare\.outputs\.external_package_source \}\}.*?target_repo:\s*\$\{\{ github\.repository \}\}.*?writable_paths:\s*\$\{\{ inputs\.writable_paths \}\}.*?verification_commands:\s*\$\{\{ needs\.prepare\.outputs\.verification_commands \}\}.*?drift_checks:\s*\$\{\{ needs\.prepare\.outputs\.drift_checks \}\}.*?success_requires_pr:\s*\$\{\{ needs\.prepare\.outputs\.success_requires_pr == \'true\' \}\}.*?access_token_repos:\s*\$\{\{ github\.repository \}\}.*?require_access_token:\s*true.*?allowed_repos:\s*\'\["\$\{\{ github\.repository \}\}"\]\'~s', 'Docs Agent must preserve the external-package, target, writable, verification, publication, and access chain.', $failures ); +agents_api_docs_agent_contract_match( $docs_workflow, '~OPENAI_API_KEY:\s*\$\{\{ secrets\.OPENAI_API_KEY \}\}\s+ACCESS_TOKEN:\s*\$\{\{ github\.token \}\}\s+EXTERNAL_PACKAGE_SOURCE_POLICY:\s*\$\{\{ secrets\.EXTERNAL_PACKAGE_SOURCE_POLICY \}\}~s', 'Docs Agent must forward caller credentials and its built-in publication token.', $failures ); + if ( $failures ) { fwrite( STDERR, "Docs Agent native workflow contract failed:\n- " . implode( "\n- ", $failures ) . "\n" ); exit( 1 ); From dc6fb39169810869b81377c56ac9d56c86b68f59 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Mon, 13 Jul 2026 14:48:33 -0400 Subject: [PATCH 3/3] Keep producer contract out of generic smoke suite --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index cba4af5..4fb1c37 100644 --- a/composer.json +++ b/composer.json @@ -132,7 +132,6 @@ "php tests/event-trigger-smoke.php", "php tests/subagents-smoke.php", "php tests/access-decision-filter-smoke.php", - "php tests/docs-agent-native-workflow-contract.php", "php tests/no-product-imports-smoke.php" ], "test": [