From 71d28ca2ef0e8d82ed727ac41cda706e3f3a4f89 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:11 +0900 Subject: [PATCH 01/10] fix: preserve numbers followed by sentence commas --- src/verify.js | 2 +- tests/e2e/cli-verification.test.js | 12 +++++++----- tests/unit/verify.test.js | 13 +++++++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/verify.js b/src/verify.js index d7577e64..4850c5b2 100644 --- a/src/verify.js +++ b/src/verify.js @@ -20,7 +20,7 @@ function numbersIn(text) { // Normalize valid grouping commas (1,200 === 1200) and strip trailing // separators so the same number in a different format is not flagged as // dropped; non-standard grouping (1,2) is preserved to avoid false negatives. - const raw = m[0].replace(/\.+$/, ''); + const raw = m[0].replace(/[.,]+$/, ''); const normalized = GROUPED_THOUSANDS_RE.test(raw) ? raw.replace(/,/g, '') : raw; if (normalized) out.add(normalized); } diff --git a/tests/e2e/cli-verification.test.js b/tests/e2e/cli-verification.test.js index dba5e757..7a420860 100644 --- a/tests/e2e/cli-verification.test.js +++ b/tests/e2e/cli-verification.test.js @@ -301,9 +301,11 @@ test('CLI --verify JSON binds scores to graded text and rejects post-verificatio // length_ratio band stays out of the way and the claim bag is what decides. 'claim-swap': 'The service retains 12 of 12 logs.', 'claim-safe-reword': 'The service retains 12 audit files.', + 'comma-reword': 'As planned, there were 5.', }; - for (const scenario of ['pass', 'hard-fail', 'malformed', 'dropped-number', 'retry-pass', 'nested-body', 'claim-swap', 'claim-safe-reword']) { - const text = scenario === 'nested-body' ? 'The service does not store drafts. It runs locally.\n' : 'The service retains 12 audit logs.'; + for (const scenario of ['pass', 'hard-fail', 'malformed', 'dropped-number', 'retry-pass', 'nested-body', 'claim-swap', 'claim-safe-reword', 'comma-reword']) { + const text = scenario === 'nested-body' ? 'The service does not store drafts. It runs locally.\n' + : scenario === 'comma-reword' ? 'There were 5, as planned.' : 'The service retains 12 audit logs.'; const dir = mkdtempSync(join(tmpdir(), 'patina-runtime-json-')); writeFileSync(join(dir, 'key'), 'test-key'); writeFileSync(join(dir, 'input.txt'), text); @@ -335,8 +337,8 @@ test('CLI --verify JSON binds scores to graded text and rejects post-verificatio '--api-key-file', join(dir, 'key'), '--base-url', `http://127.0.0.1:${server.address().port}/v1`, join(dir, 'input.txt')], { cwd: dir, timeout: 20000, env: { ...process.env, HOME: dir, USERPROFILE: dir, TMPDIR: dir } }) .then(result => ({ ...result, code: 0 }), error => error); - const success = ['pass', 'retry-pass', 'claim-safe-reword'].includes(scenario); - const singlePass = ['pass', 'dropped-number', 'nested-body', 'claim-swap', 'claim-safe-reword'].includes(scenario); + const success = ['pass', 'retry-pass', 'claim-safe-reword', 'comma-reword'].includes(scenario); + const singlePass = ['pass', 'dropped-number', 'nested-body', 'claim-swap', 'claim-safe-reword', 'comma-reword'].includes(scenario); assert.equal(result.code, success ? 0 : 4, result.stderr); const payload = JSON.parse(result.stdout); assert.equal(payload.mode, 'rewrite'); @@ -344,7 +346,7 @@ test('CLI --verify JSON binds scores to graded text and rejects post-verificatio assert.deepEqual(payload.verification, { verified: success, mps: scenario === 'malformed' ? null : scenario === 'hard-fail' ? 95 : 100, fidelity: 100, retried: !singlePass, - reason: ['pass', 'claim-safe-reword'].includes(scenario) ? 'passed' : scenario === 'retry-pass' ? 'passed-on-retry' + reason: ['pass', 'claim-safe-reword', 'comma-reword'].includes(scenario) ? 'passed' : scenario === 'retry-pass' ? 'passed-on-retry' : scenario === 'dropped-number' ? 'dropped-numbers' : scenario === 'claim-swap' ? 'numeric-claim-changed' : scenario === 'nested-body' ? 'output-changed' : 'floor-not-met', mpsFloor: 95, fidelityFloor: 95, diff --git a/tests/unit/verify.test.js b/tests/unit/verify.test.js index 1083cbc2..942b9992 100644 --- a/tests/unit/verify.test.js +++ b/tests/unit/verify.test.js @@ -35,6 +35,19 @@ test('deterministicMeaningGuard preserves non-standard grouping so a dropped 1,2 assert.ok(warnings.some((w) => /numbers/.test(w)), warnings.join(' | ')); }); +test('meaning safety ignores sentence punctuation after a preserved number', () => { + for (const [original, rewrite] of [ + ['There were 5, as planned.', 'As planned, there were 5.'], + ['We reached 1,200, as planned.', 'As planned, we reached 1200.'], + ['The value was 1.25, as expected.', 'As expected, the value was 1.25.'], + ]) { + assert.deepEqual(deterministicMeaningGuard(original, rewrite), []); + assert.equal(assessRewriteMeaningSafety(original, rewrite).ok, true); + assert.equal(assessRewriteMeaningSafety(rewrite, original).ok, true); + } + assert.equal(assessRewriteMeaningSafety('There were 5, as planned.', 'There were 6.').ok, false); +}); + test('assessRewriteMeaningSafety keeps vanished digits as dropped-numbers', () => { const result = assessRewriteMeaningSafety( 'The service retains 12 audit logs.', From cd465853c8d20d08242a580c3688350219254bb5 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:11 +0900 Subject: [PATCH 02/10] fix: guard private configuration and scoped agent files --- docs/QA.md | 6 ++ package.json | 11 +++ scripts/check-no-private-assets.mjs | 20 +++- tests/e2e/check-no-private-assets.test.js | 101 ++++++++++++--------- tests/unit/check-no-private-assets.test.js | 15 ++- 5 files changed, 107 insertions(+), 46 deletions(-) diff --git a/docs/QA.md b/docs/QA.md index 10111930..f231a77c 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -39,6 +39,12 @@ result `inconclusive` until an approved command exists. | `release-safety` | `npm run release:check` and `npm run check:no-private-assets` | Release metadata and private-asset boundary | | `live-model` | `npm run quality:live` | Opt-in model-backed quality; approval and budget required | +The private-asset gate checks tracked and packaged paths, including `.env` and `.env.*`, +credentials, key files, `docs/internal/`, and client-specific agent rules. +Only root `AGENTS.md` and the sanitized root `.env.example` are tracking +exceptions. This is a path boundary, not a secret-content scanner; inspect +public fixture contents before committing them. + Other package scripts may fit the changed boundary (for example, `benchmark:robustness` or `quality:adversarial-mps`). Record the exact command and arguments rather than assuming a similarly named diff --git a/package.json b/package.json index 59d31b9e..140d9d18 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,17 @@ "!**/AGENTS.md", "!CLAUDE.md", "!**/CLAUDE.md", + "!**/GEMINI.md", + "!**/.env", + "!**/.env.*", + "!**/*.key", + "!**/*.pem", + "!**/credentials.json", + "!**/.credentials.json", + "!**/credentials/**", + "!**/secrets/**", + "!**/.aws/**", + "!**/.ssh/**", "!BOOTSTRAP.md", "!**/BOOTSTRAP.md", "!IDENTITY.md", diff --git a/scripts/check-no-private-assets.mjs b/scripts/check-no-private-assets.mjs index b4244565..12fc4473 100644 --- a/scripts/check-no-private-assets.mjs +++ b/scripts/check-no-private-assets.mjs @@ -30,6 +30,17 @@ export const FORBIDDEN_GLOBS = Object.freeze([ '**/reinforced/**', // any `reinforced/` directory (reinforced assets) '**/corpus/**', // private corpus directories 'server/**', // private service/server implementation + 'docs/internal/**', + '**/.env', + '**/.env.*', + '**/*.key', + '**/*.pem', + '**/credentials.json', + '**/.credentials.json', + '**/credentials/**', + '**/secrets/**', + '**/.aws/**', + '**/.ssh/**', ]); // Agent instructions and runtime workspaces are development-only. They are @@ -39,6 +50,7 @@ export const FORBIDDEN_GLOBS = Object.freeze([ const DEVELOPMENT_ONLY_GLOBS = Object.freeze([ '**/AGENTS.md', '**/CLAUDE.md', + '**/GEMINI.md', '**/BOOTSTRAP.md', '**/IDENTITY.md', '**/USER.md', @@ -58,11 +70,11 @@ const DEVELOPMENT_ONLY_GLOBS = Object.freeze([ ]); // Keep package and git checks explicit: the package may never contain -// development-only instructions, while git tracking allows exactly the -// approved public root rule below. +// development-only instructions, while git tracking allows the +// approved public root rule and sanitized environment template below. export const PACKED_FORBIDDEN_GLOBS = Object.freeze([...FORBIDDEN_GLOBS, ...DEVELOPMENT_ONLY_GLOBS]); export const TRACKED_FORBIDDEN_GLOBS = Object.freeze([...FORBIDDEN_GLOBS, ...DEVELOPMENT_ONLY_GLOBS]); -export const TRACKED_ALLOWED_PATHS = Object.freeze(['AGENTS.md']); +export const TRACKED_ALLOWED_PATHS = Object.freeze(['AGENTS.md', '.env.example']); /** * Compile a path glob into an anchored RegExp. @@ -142,7 +154,7 @@ export function matchForbidden(paths, globs = FORBIDDEN_GLOBS, allowedPaths = [] * @param {string[]} [sources.packedFiles=[]] Files that npm would publish (repo-relative). * @param {string[]} [sources.trackedFiles=[]] Git-tracked files. * The packed list rejects development-only instructions and runtime workspaces. - * The tracked list allows only the approved public root `AGENTS.md` exception. + * The tracked list allows the approved root rule and sanitized environment template. * @returns {{ok: boolean, violations: Array<{path: string, pattern: string, source: string}>, counts: {packed: number, tracked: number}}} Gate result. * @example * runGate({ packedFiles: ['src/index.js'], trackedFiles: ['src/index.js'] }).ok; // true diff --git a/tests/e2e/check-no-private-assets.test.js b/tests/e2e/check-no-private-assets.test.js index af719010..26174db1 100644 --- a/tests/e2e/check-no-private-assets.test.js +++ b/tests/e2e/check-no-private-assets.test.js @@ -1,22 +1,46 @@ import { describe, it } from 'node:test'; import assert from 'node:assert'; import { spawnSync } from 'node:child_process'; -import { existsSync, rmSync, writeFileSync } from 'node:fs'; -import { dirname, resolve } from 'node:path'; +import { copyFileSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_ROOT = resolve(__dirname, '../..'); -const GATE_SCRIPT = resolve(REPO_ROOT, 'scripts/check-no-private-assets.mjs'); -function runGateScript() { - return spawnSync(process.execPath, [GATE_SCRIPT], { - cwd: REPO_ROOT, +function runGateScript(root = REPO_ROOT) { + return spawnSync(process.execPath, [resolve(root, 'scripts/check-no-private-assets.mjs')], { + cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, }); } +function git(root, args) { + const result = spawnSync('git', args, { cwd: root, encoding: 'utf8' }); + assert.strictEqual(result.status, 0, result.stderr); +} + +// Exercise the real allowlists and gate without planting files in a checkout +// that another test may be packaging concurrently. +function packageFixture(t) { + const root = mkdtempSync(join(tmpdir(), 'patina-leak-gate-')); + t.after(() => rmSync(root, { recursive: true, force: true })); + for (const path of ['src', 'scripts', 'packages/patina-humanizer/bin']) { + mkdirSync(resolve(root, path), { recursive: true }); + } + for (const path of ['package.json', 'packages/patina-humanizer/package.json', 'scripts/check-no-private-assets.mjs']) { + copyFileSync(resolve(REPO_ROOT, path), resolve(root, path)); + } + writeFileSync(resolve(root, 'SKILL.md'), '# Synthetic product fixture\n'); + writeFileSync(resolve(root, 'src/index.js'), 'export const fixture = true;\n'); + writeFileSync(resolve(root, 'packages/patina-humanizer/bin/patina-humanizer.js'), '#!/usr/bin/env node\n'); + git(root, ['init', '--quiet']); + git(root, ['add', '--force', 'package.json', 'SKILL.md', 'src', 'scripts', 'packages']); + return root; +} + describe('leak gate (end to end): real npm pack + git enumeration', () => { it('passes on the clean public repo', { timeout: 120000 }, () => { const result = runGateScript(); @@ -24,43 +48,38 @@ describe('leak gate (end to end): real npm pack + git enumeration', () => { assert.match(result.stdout, /leak gate OK/); }); - it('omits a scoped development rule from the actual npm pack', { timeout: 120000 }, () => { - const planted = resolve(REPO_ROOT, 'src/AGENTS.md'); - writeFileSync(planted, '# fixture-only scoped development rule\n'); - try { - // Bare npm is npm.cmd on win32 (needs a shell; CVE-2024-27980). - const pack = process.platform === 'win32' - ? spawnSync('npm.cmd', ['pack', '--dry-run', '--json', '--ignore-scripts'], { - cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, shell: true }) - : spawnSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], { - cwd: REPO_ROOT, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 }); - assert.strictEqual(pack.status, 0, `npm pack should succeed:\n${pack.stdout}\n${pack.stderr}`); - const packed = JSON.parse(pack.stdout)[0].files.map((file) => file.path); - assert.ok(packed.includes('SKILL.md'), 'product SKILL.md must remain packaged'); - assert.ok(!packed.includes('src/AGENTS.md'), 'scoped development rules must stay out of the package'); - - const gate = runGateScript(); - assert.strictEqual(gate.status, 0, `gate should pass when npm excludes the fixture:\n${gate.stdout}\n${gate.stderr}`); - } finally { - rmSync(planted, { force: true }); + it('omits development instructions and credentials from the actual npm pack', { timeout: 120000 }, (t) => { + const root = packageFixture(t); + const filenames = ['AGENTS.md', 'CLAUDE.md', 'GEMINI.md', '.env.local', 'credentials.json', 'fixture.key', 'fixture.pem']; + for (const filename of filenames) { + writeFileSync(resolve(root, 'src', filename), 'synthetic private fixture\n', { flag: 'wx' }); + } + const pack = spawnSync(process.platform === 'win32' ? 'npm.cmd' : 'npm', ['pack', '--dry-run', '--json', '--ignore-scripts'], { + cwd: root, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024, shell: process.platform === 'win32', + }); + assert.strictEqual(pack.status, 0, `npm pack should succeed:\n${pack.stdout}\n${pack.stderr}`); + const packed = JSON.parse(pack.stdout)[0].files.map((file) => file.path); + assert.ok(packed.includes('SKILL.md'), 'product SKILL.md must remain packaged'); + for (const filename of filenames) { + assert.ok(!packed.includes(`src/${filename}`), `${filename} must stay out of the package`); } - assert.ok(!existsSync(planted), 'planted fixture must be cleaned up'); + const gate = runGateScript(root); + assert.strictEqual(gate.status, 0, `gate should pass when npm excludes the fixtures:\n${gate.stdout}\n${gate.stderr}`); + + git(root, ['add', '--force', 'src/GEMINI.md']); + const tracked = runGateScript(root); + assert.strictEqual(tracked.status, 1, 'git-tracked development rules must fail even when excluded from npm'); + assert.match(tracked.stderr, /\[git\] src\/GEMINI\.md/); }); - it('fails when a private asset is planted inside a published directory', { timeout: 120000 }, () => { - // `src/` is wholesale-included via package.json `files[]`, so npm pack will - // enumerate this file even though it is never git-tracked. - const planted = resolve(REPO_ROOT, 'src/__leak_probe__.private.js'); - writeFileSync(planted, '// planted leak-gate probe fixture; the test removes it.\n'); - try { - const result = runGateScript(); - assert.strictEqual(result.status, 1, 'gate must fail when a private asset is packable'); - assert.match(result.stderr, /leak gate FAILED/); - assert.match(result.stderr, /__leak_probe__\.private\.js/); - assert.match(result.stderr, /\*\*\/\*\.private\.\*/); - } finally { - rmSync(planted, { force: true }); - } - assert.ok(!existsSync(planted), 'planted fixture must be cleaned up'); + it('fails when a private asset is planted inside a published directory', { timeout: 120000 }, (t) => { + const root = packageFixture(t); + // `src/` is wholesale-included, so npm enumerates this untracked file. + writeFileSync(resolve(root, 'src/__leak_probe__.private.js'), '// synthetic leak-gate probe\n', { flag: 'wx' }); + const result = runGateScript(root); + assert.strictEqual(result.status, 1, 'gate must fail when a private asset is packable'); + assert.match(result.stderr, /leak gate FAILED/); + assert.match(result.stderr, /__leak_probe__\.private\.js/); + assert.match(result.stderr, /\*\*\/\*\.private\.\*/); }); }); diff --git a/tests/unit/check-no-private-assets.test.js b/tests/unit/check-no-private-assets.test.js index e70bf30f..4411c406 100644 --- a/tests/unit/check-no-private-assets.test.js +++ b/tests/unit/check-no-private-assets.test.js @@ -80,11 +80,24 @@ describe('leak gate: forbidden pattern set', () => { }); describe('leak gate: source-specific development instructions', () => { + it('rejects private configuration, credentials, and maintainer paths in either source', () => { + for (const path of ['src/GEMINI.md', 'GEMINI.md', 'docs/internal/review.md', + 'src/.env.local', '.env', 'src/credentials.json', 'src/key.pem', + 'src/credentials/key.json', 'src/.aws/config', 'src/.ssh/id_rsa']) { + for (const source of ['packedFiles', 'trackedFiles']) { + const result = runGate({ [source]: [path] }); + assert.equal(result.ok, false, `${source}: ${path}`); + assert.equal(result.violations.length, 1); + } + } + assert.equal(runGate({ trackedFiles: ['AGENTS.md', '.env.example'] }).ok, true); + }); + it('keeps the baseline matcher separate from development-only paths', () => { assert.deepStrictEqual(matchForbidden(['src/AGENTS.md', 'src/CLAUDE.md']), []); assert.ok(PACKED_FORBIDDEN_GLOBS.includes('**/AGENTS.md')); assert.ok(TRACKED_FORBIDDEN_GLOBS.includes('**/AGENTS.md')); - assert.deepStrictEqual(TRACKED_ALLOWED_PATHS, ['AGENTS.md']); + assert.deepStrictEqual(TRACKED_ALLOWED_PATHS, ['AGENTS.md', '.env.example']); }); it('allows the approved public root rule in git but rejects scoped rules in both sources', () => { From 830671e33b37a736b4cd8df10402e6143e339b5f Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:29 +0900 Subject: [PATCH 03/10] test: isolate offline tests and add browser and install CI profiles --- .github/workflows/test.yml | 33 ++++++++++++++++ docs/HARNESS.md | 4 +- docs/QA.md | 15 ++++++-- docs/WORKFLOW.md | 4 +- docs/integrations/release.md | 4 +- package.json | 1 + src/backends/index.js | 17 +++++++-- tests/e2e/backends.test.js | 20 +++++++--- tests/e2e/release-artifacts.test.js | 26 ------------- tests/integration/release-install.test.js | 32 ++++++++++++++++ tests/unit/backend-agy.test.js | 46 ++++++++++++----------- tests/unit/ci-workflow.test.js | 11 +++++- 12 files changed, 149 insertions(+), 64 deletions(-) create mode 100644 tests/integration/release-install.test.js diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 22dcd1ce..dce298d1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,6 +58,8 @@ jobs: - name: Run tests if: matrix.node-version != '18.1.0' run: npm test + env: + NPM_CONFIG_OFFLINE: "true" - name: Run minimum Node smoke if: matrix.node-version == '18.1.0' @@ -169,3 +171,34 @@ jobs: echo '::error::tests/unit/quota-redis.test.js did not execute against real Redis (non-zero skip count).' exit 1 } + + browser: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + - name: Install dependencies + run: npm ci + - name: Install Chromium and system dependencies + run: npx --no-install playwright install --with-deps chromium + - name: Browser regression with local transport fixtures + run: npm run test:browser + + release-install: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + - name: Install dependencies + run: npm ci + - name: Registry-backed package installation smoke + # Explicit network profile: ordinary npm test never installs dependencies. + run: npm run test:release-install diff --git a/docs/HARNESS.md b/docs/HARNESS.md index 6f998cbd..110d324f 100644 --- a/docs/HARNESS.md +++ b/docs/HARNESS.md @@ -87,7 +87,9 @@ analysis below ablates each one to report its marginal contribution. | Tool | Command | Purpose | |---|---|---| | Lint | `npm run lint` | syntax + eslint + tsc + cspell | -| Tests | `npm test` | unit + e2e (`node --test`) | +| Tests | `npm test` | unit + e2e (`node --test`), no registry installation | +| Browser fixtures | `npm run test:browser` | Chromium with local transport fixtures | +| Package install | `npm run test:release-install` | Explicit online npm-registry smoke of local root/alias tarballs | | Release metadata | `npm run release:check` | version sync across all version-bearing surfaces | | Private-asset leak | `npm run check:no-private-assets` | no private/vendor text in the npm tarball or tree | | Prose score gate | `patina-score` | hot-paragraph ratio CI gate (default 30) | diff --git a/docs/QA.md b/docs/QA.md index f231a77c..44a7fa1b 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -28,8 +28,9 @@ result `inconclusive` until an approved command exists. |---|---|---| | `unit` | `npm run test:unit` | Unit tests | | `e2e` | `npm run test:e2e` | End-to-end tests; inspect whether transport is mocked | -| `deterministic` | `npm test` | Unit plus e2e (`node --test`) | +| `deterministic` | `npm test` | Unit plus e2e (`node --test`); no registry installation or live backend probes | | `static` | `npm run lint` | Syntax, ESLint, TypeScript, configured CSpell, and AST import boundaries; spellcheck uses this checkout as its gitignore root | +| `release-install` | `npm run test:release-install` | Explicitly online public-registry install of local root/alias tarballs; separate from unit/e2e | | `browser-fixture` | `npm run test:browser` | Real Chromium, local fixture transport, and checked-JS browser assertions; Node 24 development profile, not live-model quality | | `regression` | `npm run benchmark` | Fixed quality fixtures | | `quality-report` | `npm run benchmark:report` | Existing benchmark report generation | @@ -39,6 +40,14 @@ result `inconclusive` until an approved command exists. | `release-safety` | `npm run release:check` and `npm run check:no-private-assets` | Release metadata and private-asset boundary | | `live-model` | `npm run quality:live` | Opt-in model-backed quality; approval and budget required | +CI provisions dependencies before running the profiles. The `browser` job +installs Chromium, then runs local transport fixtures; the `release-install` +job confines registry reads to its isolated runner. Local registry installation +requires explicit authorization. The release workflow also exercises installation +when it builds and verifies the exact publication tarballs. Tests of backend +listing inject status probes; adapter authentication tests use owned fixtures. +No profile here proves live-model quality. + The private-asset gate checks tracked and packaged paths, including `.env` and `.env.*`, credentials, key files, `docs/internal/`, and client-specific agent rules. Only root `AGENTS.md` and the sanitized root `.env.example` are tracking @@ -141,8 +150,8 @@ makes no LLM call. Read the lifecycle numbers carefully. On platforms without POSIX process groups the cancellation/isolation tests **skip**, which is *absent coverage*, -not a pass; the `backend-agy` launch tests in the same step skip for a -different reason (host Antigravity settings that widen permissions). The +not a pass; the `backend-agy` launch tests also skip on Windows because their fake CLI +requires a POSIX shebang. Antigravity settings tests use an owned home. The receipt therefore carries failing names and counts, and whoever reads it must state which promise each skip removes rather than treating a skip total as platform support. `doctor` exit 1 with a valid report ("no usable backend") is diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index e68df932..ee6f1505 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -22,7 +22,9 @@ runs a Node 18.1.0 smoke check plus full tests on Node 20, 22, and floating `lts/*`. Branch protection requires the exact `test (lts/*)` name, so the matrix keeps the floating entry rather than a fixed 24; review fixed coverage when LTS advances. None of this changes the product engine requirement -(`>=18.1.0`). +(`>=18.1.0`). Browser fixtures run on Node 24 with Chromium and blocked +external browser requests. `release-install` is a separate, explicitly online +npm-registry installation smoke; ordinary unit/e2e tests do not install packages. ## The branch model diff --git a/docs/integrations/release.md b/docs/integrations/release.md index ea5b1f5e..c8db4c6a 100644 --- a/docs/integrations/release.md +++ b/docs/integrations/release.md @@ -47,7 +47,9 @@ dependencies (currently `patina-humanizer`, with paths resolving to the two supplied tarballs, so neither package can silently fall back to a registry version. The fixture verifies that the alias's exact `patina-cli` dependency resolves to the -supplied root tarball and runs both CLI `--version` smoke checks. The verified +supplied root tarball and runs both CLI `--version` smoke checks. The same +registry-backed test is available as `npm run test:release-install` and runs in +its own PR CI job; it is not part of ordinary `npm test`. The verified directory is uploaded as an artifact named for the commit SHA. The build output directory and smoke fixture are required to be new or empty; the script refuses to delete caller-owned files. diff --git a/package.json b/package.json index 140d9d18..78e43ac6 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:unit": "node -r ./tests/helpers/real-tmpdir.cjs --test tests/unit/*.test.js", "launch-config:generate": "node scripts/generate-launch-config.mjs", "test:e2e": "node -r ./tests/helpers/real-tmpdir.cjs --test tests/e2e/*.test.js", + "test:release-install": "node -r ./tests/helpers/real-tmpdir.cjs --test tests/integration/release-install.test.js", "test:browser": "node -r ./tests/helpers/real-tmpdir.cjs --test tests/browser/*.test.js", "smoke:platform": "node scripts/platform-smoke.mjs", "benchmark": "node tests/quality/benchmark.mjs", diff --git a/src/backends/index.js b/src/backends/index.js index 5dc534b6..3735237b 100644 --- a/src/backends/index.js +++ b/src/backends/index.js @@ -74,11 +74,20 @@ const BACKEND_META = { }, }; -export function listBackends() { +function probeBackendStatus(backend) { + return { + available: backend.isAvailable(), + authenticated: backend.isAuthenticated(), + authHint: backend.authHint(), + }; +} + +export function listBackends({ probeStatus = probeBackendStatus } = {}) { return Object.keys(REGISTRY).map((key) => { const b = REGISTRY[key]; const meta = BACKEND_META[key]; const safety = getBackendSafety(key); + const status = probeStatus(b); return { name: key, kind: meta.kind, @@ -89,9 +98,9 @@ export function listBackends() { maxRetries: safety.maxRetries, promptMode: safety.promptMode, agentRuntime: safety.agentRuntime, - available: b.isAvailable(), - authenticated: b.isAuthenticated(), - authHint: b.authHint(), + available: status.available, + authenticated: status.authenticated, + authHint: status.authHint, loginCommand: b.loginCommand || null, installHint: b.installHint || null, }; diff --git a/tests/e2e/backends.test.js b/tests/e2e/backends.test.js index 62edf665..18754082 100644 --- a/tests/e2e/backends.test.js +++ b/tests/e2e/backends.test.js @@ -367,9 +367,17 @@ describe('Backend Fallback Chain', () => { }); }); +// Status probes have their own adapter fixture tests. Listing must never probe +// a developer's CLI installation, credential files, or macOS Keychain. +const fixtureStatus = (backend) => ({ + available: backend.name === 'openai-http', + authenticated: backend.name === 'gemini-cli', + authHint: `fixture auth for ${backend.name}`, +}); + describe('Backend Listing', () => { it('returns at least openai-http, codex-cli, claude-cli, gemini-cli', () => { - const list = listBackends(); + const list = listBackends({ probeStatus: fixtureStatus }); const names = list.map((b) => b.name); for (const expected of ['openai-http', 'codex-cli', 'claude-cli', 'gemini-cli']) { assert.ok(names.includes(expected), `expected backend ${expected}`); @@ -377,21 +385,21 @@ describe('Backend Listing', () => { }); it('reports openai-http as always available (HTTP, no install check)', () => { - const list = listBackends(); + const list = listBackends({ probeStatus: fixtureStatus }); const http = list.find((b) => b.name === 'openai-http'); assert.strictEqual(http.available, true); }); it('reports authenticated status for each backend', () => { - const list = listBackends(); + const list = listBackends({ probeStatus: fixtureStatus }); for (const b of list) { - assert.strictEqual(typeof b.authenticated, 'boolean'); - assert.strictEqual(typeof b.authHint, 'string'); + assert.strictEqual(b.authenticated, b.name === 'gemini-cli'); + assert.strictEqual(b.authHint, `fixture auth for ${b.name}`); } }); it('reports default best-model ids for user-facing backend status', () => { - const byName = new Map(listBackends().map((b) => [b.name, b.defaultModel])); + const byName = new Map(listBackends({ probeStatus: fixtureStatus }).map((b) => [b.name, b.defaultModel])); assert.strictEqual(byName.get('openai-http'), DEFAULT_BEST_MODELS.openai); assert.strictEqual(byName.get('codex-cli'), DEFAULT_BEST_MODELS.codexCli); assert.strictEqual(byName.get('claude-cli'), DEFAULT_BEST_MODELS.claudeCli); diff --git a/tests/e2e/release-artifacts.test.js b/tests/e2e/release-artifacts.test.js index 27492fd8..b750ff55 100644 --- a/tests/e2e/release-artifacts.test.js +++ b/tests/e2e/release-artifacts.test.js @@ -6,34 +6,8 @@ import { test } from 'node:test'; import { buildReleaseArtifacts, verifyReleaseArtifacts, - runLocalInstallSmoke, } from '../../scripts/release-artifacts.mjs'; -test('real root and alias tarballs install together and run both CLIs', { timeout: 120_000 }, () => { - const outputDir = mkdtempSync(join(tmpdir(), 'patina-release-artifacts-')); - try { - const built = buildReleaseArtifacts({ - outputDir, - sourceSHA: 'e2e-source-sha', - }); - const verified = verifyReleaseArtifacts({ - outputDir, - sourceSHA: 'e2e-source-sha', - expectedVersion: built.manifest.version, - }); - const smoke = runLocalInstallSmoke({ - outputDir, - artifacts: verified.artifacts, - }); - assert.deepEqual(smoke.versions, { - root: `patina ${built.manifest.version}`, - alias: `patina ${built.manifest.version}`, - }); - } finally { - rmSync(outputDir, { recursive: true, force: true }); - } -}); - test('real tarball verification rejects bytes changed after the manifest', () => { const outputDir = mkdtempSync(join(tmpdir(), 'patina-release-tamper-')); try { diff --git a/tests/integration/release-install.test.js b/tests/integration/release-install.test.js new file mode 100644 index 00000000..f57ad7f4 --- /dev/null +++ b/tests/integration/release-install.test.js @@ -0,0 +1,32 @@ +import assert from 'node:assert/strict'; +import { mkdtempSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { test } from 'node:test'; +import { buildReleaseArtifacts, verifyReleaseArtifacts, runLocalInstallSmoke } from '../../scripts/release-artifacts.mjs'; + +// Explicit registry-backed profile; excluded from the offline unit/e2e glob. +test('real root and alias tarballs install together and run both CLIs', { timeout: 120_000 }, () => { + const outputDir = mkdtempSync(join(tmpdir(), 'patina-release-artifacts-')); + try { + const built = buildReleaseArtifacts({ + outputDir, + sourceSHA: 'e2e-source-sha', + }); + const verified = verifyReleaseArtifacts({ + outputDir, + sourceSHA: 'e2e-source-sha', + expectedVersion: built.manifest.version, + }); + const smoke = runLocalInstallSmoke({ + outputDir, + artifacts: verified.artifacts, + }); + assert.deepEqual(smoke.versions, { + root: `patina ${built.manifest.version}`, + alias: `patina ${built.manifest.version}`, + }); + } finally { + rmSync(outputDir, { recursive: true, force: true }); + } +}); diff --git a/tests/unit/backend-agy.test.js b/tests/unit/backend-agy.test.js index d6bc0cc6..17af04ba 100644 --- a/tests/unit/backend-agy.test.js +++ b/tests/unit/backend-agy.test.js @@ -1,4 +1,4 @@ -import { chmodSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { test } from 'node:test'; @@ -43,35 +43,30 @@ const FAKE_AGY = [ async function withFakeAgy(mode, fn) { const binDir = mkdtempSync(join(tmpdir(), 'patina-agy-fake-')); - const oldPath = process.env.PATH; - const oldMode = process.env.FAKE_AGY_MODE; + const saved = Object.fromEntries(['PATH', 'FAKE_AGY_MODE', 'HOME', 'USERPROFILE'].map(key => [key, process.env[key]])); + const fixtureHome = join(binDir, 'home'); try { + mkdirSync(fixtureHome); + process.env.HOME = fixtureHome; + process.env.USERPROFILE = fixtureHome; + assert.equal(agyCli.agySettingsPath(), join(fixtureHome, '.gemini', 'antigravity-cli', 'settings.json')); const path = join(binDir, 'agy'); writeFileSync(path, FAKE_AGY); chmodSync(path, 0o755); - process.env.PATH = `${binDir}:${oldPath || ''}`; + process.env.PATH = `${binDir}:${saved.PATH || ''}`; process.env.FAKE_AGY_MODE = mode; - return await fn(); + return await fn(fixtureHome); } finally { - process.env.PATH = oldPath; - if (oldMode === undefined) delete process.env.FAKE_AGY_MODE; - else process.env.FAKE_AGY_MODE = oldMode; + for (const [key, value] of Object.entries(saved)) { + if (value === undefined) delete process.env[key]; + else process.env[key] = value; + } rmSync(binDir, { recursive: true, force: true }); } } -// invoke() reads the real ~/.gemini/antigravity-cli/settings.json and refuses -// to launch when it widens headless permissions; os.homedir() cannot be -// redirected here, so the launch-path tests skip on such hosts instead of -// flaking. The refusal itself is covered by the settings test below. -function hostSettingsProblem() { - try { - agyCli.assertAgySettingsSafe(agyCli.readAgySettings()); - return false; - } catch (err) { - return err.message; - } -} +// Each launch reads settings from an owned home, independent of host login +// or permissions. The adapter's production settings check remains active. // The fake `agy` is an extensionless POSIX shebang script joined to PATH with // ':'. Windows never resolves an extensionless name through PATHEXT and (since // the Node CVE-2024-27980 fix) refuses to spawn a .cmd shim without a shell, @@ -80,7 +75,7 @@ function hostSettingsProblem() { // stream parsing, settings guard) still runs on win32. const launchSkip = process.platform === 'win32' ? 'fake agy CLI shim requires POSIX shebang semantics; live Windows launch unverified' - : hostSettingsProblem(); + : false; function argValue(args, flag) { const index = args.indexOf(flag); @@ -147,6 +142,15 @@ test('agy-cli fails closed on empty, error, tool-using, garbage and non-zero out }); }); +test('agy-cli refuses unsafe settings in the owned launch fixture', { skip: launchSkip }, async () => { + await withFakeAgy('success', async (fixtureHome) => { + const settingsDir = join(fixtureHome, '.gemini', 'antigravity-cli'); + mkdirSync(settingsDir, { recursive: true }); + writeFileSync(join(settingsDir, 'settings.json'), JSON.stringify({ toolPermission: 'always-proceed' })); + await assert.rejects(agyCli.invoke({ prompt: 'x' }), /refusing to run[\s\S]*always-proceed/); + }); +}); + test('agy-cli maps a non-finite timeout to an effectively unlimited --print-timeout', async () => { assert.strictEqual(agyCli.agyPrintTimeout(30_000), '30s'); assert.strictEqual(agyCli.agyPrintTimeout(1), '1s'); diff --git a/tests/unit/ci-workflow.test.js b/tests/unit/ci-workflow.test.js index 8d763b72..d4136122 100644 --- a/tests/unit/ci-workflow.test.js +++ b/tests/unit/ci-workflow.test.js @@ -63,7 +63,7 @@ test('CI workflow YAML configuration contracts retain protected checks, matrix, assert.equal(eventConfig['paths-ignore'], undefined, `${eventName} must not add path filters`); } assert.ok(Object.prototype.hasOwnProperty.call(events, 'workflow_dispatch')); - assert.deepEqual(Object.keys(jobs).sort(), ['lint', 'quality', 'test']); + assert.deepEqual(Object.keys(jobs).sort(), ['browser', 'lint', 'quality', 'release-install', 'test']); assert.equal(jobs.lint.name, undefined, 'lint check identity must remain the job id'); assert.equal(jobs.quality.name, undefined, 'quality check identity must remain the job id'); @@ -121,6 +121,15 @@ test('CI workflow YAML configuration contracts retain protected checks, matrix, assertExecutableLine(jobs.quality, gate, 'quality'); } + assert.equal(jobs.test.steps.find(step => step.name === 'Run tests').env.NPM_CONFIG_OFFLINE, 'true'); + assertExecutableLine(jobs.browser, 'npm ci', 'browser'); + assertExecutableLine(jobs.browser, 'npx --no-install playwright install --with-deps chromium', 'browser'); + assertExecutableLine(jobs.browser, 'npm run test:browser', 'browser'); + assertExecutableLine(jobs['release-install'], 'npm run test:release-install', 'release-install'); + const pkg = JSON.parse(readFileSync(resolve(REPO_ROOT, 'package.json'), 'utf8')); + assert.equal(pkg.scripts['test:release-install'], 'node -r ./tests/helpers/real-tmpdir.cjs --test tests/integration/release-install.test.js'); + assert.doesNotMatch(pkg.scripts.test, /integration/); + const driftCheck = jobs.quality.steps.find((step) => step.name === 'Benchmark report drift check'); assert.equal(driftCheck?.run?.trim(), [ `git diff --exit-code -I '"generatedAt":' -I '"benchmarkGeneratedAt":' -I 'Generated at:' -I '"nodeVersion":' -I '^- Node: ' -- docs/benchmarks || {`, From 2073285024cdd76f276c7d1104fd35e0b2cbcd04 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:29 +0900 Subject: [PATCH 04/10] docs: link issue guidance to the default branch --- .github/ISSUE_TEMPLATE/benchmark_corpus.yml | 4 ++-- .github/ISSUE_TEMPLATE/bug_report.yml | 4 ++-- .github/ISSUE_TEMPLATE/calibration_concern.yml | 4 ++-- .github/ISSUE_TEMPLATE/false_positive.yml | 4 ++-- .github/ISSUE_TEMPLATE/feature_request.yml | 4 ++-- .github/ISSUE_TEMPLATE/pattern_proposal.yml | 4 ++-- .github/ISSUE_TEMPLATE/research_proposal.yml | 4 ++-- tests/unit/issue-forms.test.js | 2 +- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/benchmark_corpus.yml b/.github/ISSUE_TEMPLATE/benchmark_corpus.yml index 458a2cee..23a74c3a 100644 --- a/.github/ISSUE_TEMPLATE/benchmark_corpus.yml +++ b/.github/ISSUE_TEMPLATE/benchmark_corpus.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: kind attributes: @@ -39,7 +39,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4b54a2a0..7c350835 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,7 @@ body: attributes: value: | Thanks for reporting a bug. Please avoid sharing private or confidential text. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: input id: version attributes: @@ -56,7 +56,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/calibration_concern.yml b/.github/ISSUE_TEMPLATE/calibration_concern.yml index 83b8b992..0e14b80d 100644 --- a/.github/ISSUE_TEMPLATE/calibration_concern.yml +++ b/.github/ISSUE_TEMPLATE/calibration_concern.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: concern_type attributes: @@ -46,7 +46,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/false_positive.yml b/.github/ISSUE_TEMPLATE/false_positive.yml index fd7697f6..b48eed4f 100644 --- a/.github/ISSUE_TEMPLATE/false_positive.yml +++ b/.github/ISSUE_TEMPLATE/false_positive.yml @@ -9,7 +9,7 @@ body: Thanks for helping calibrate patina. Please share only text you are allowed to publish in a GitHub issue. Good reports include the exact paragraph that fired, the score output, and enough context to tell whether the text should become an allowlist/fixture case. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: language attributes: @@ -99,7 +99,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 9ddf88fe..d40b2eb8 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -7,7 +7,7 @@ body: attributes: value: | Keep the request focused on the user workflow. Avoid pasting private writing samples. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: area attributes: @@ -40,7 +40,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/pattern_proposal.yml b/.github/ISSUE_TEMPLATE/pattern_proposal.yml index 097da14c..6b8b0c17 100644 --- a/.github/ISSUE_TEMPLATE/pattern_proposal.yml +++ b/.github/ISSUE_TEMPLATE/pattern_proposal.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: language attributes: @@ -67,7 +67,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/.github/ISSUE_TEMPLATE/research_proposal.yml b/.github/ISSUE_TEMPLATE/research_proposal.yml index 3e9f5077..6dbc0ac0 100644 --- a/.github/ISSUE_TEMPLATE/research_proposal.yml +++ b/.github/ISSUE_TEMPLATE/research_proposal.yml @@ -6,7 +6,7 @@ body: - type: markdown attributes: value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. + See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. Cancelled human-evaluation and human-labelled SNS studies require a new explicit owner request; maintenance work does not restart them. - type: dropdown id: research_type @@ -47,7 +47,7 @@ body: id: acceptance attributes: label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. + description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. placeholder: | Scope: Non-goals: diff --git a/tests/unit/issue-forms.test.js b/tests/unit/issue-forms.test.js index 1a0de3ae..dfa8ccd8 100644 --- a/tests/unit/issue-forms.test.js +++ b/tests/unit/issue-forms.test.js @@ -6,7 +6,7 @@ import yaml from 'js-yaml'; import test from 'node:test'; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); -const POLICY_URL = 'https://github.com/devswha/patina/blob/dev/docs/WORKFLOW.md'; +const POLICY_URL = 'https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md'; const FORM_PATHS = [ '.github/ISSUE_TEMPLATE/bug_report.yml', '.github/ISSUE_TEMPLATE/feature_request.yml', From dff5280bc713e85549c5d2719fc847230e271562 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:29 +0900 Subject: [PATCH 05/10] chore: converge development and release preparation on main --- .github/dependabot.yml | 4 +- .github/workflows/test.yml | 4 +- AGENTS.md | 2 +- CONTRIBUTING.md | 8 +- CONTRIBUTING_KR.md | 8 +- docs/QA.md | 2 +- docs/WORKFLOW.md | 146 ++++++++++++----------- docs/integrations/release.md | 16 ++- docs/operations/README.md | 2 +- docs/operations/rollback-drills.md | 4 +- tests/unit/ci-workflow.test.js | 4 +- tests/unit/maintenance-workflows.test.js | 4 +- 12 files changed, 104 insertions(+), 100 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1ef43d66..25816db3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,7 +2,7 @@ version: 2 updates: - package-ecosystem: npm directory: / - target-branch: dev + target-branch: main schedule: interval: weekly open-pull-requests-limit: 1 @@ -14,7 +14,7 @@ updates: - minor - package-ecosystem: github-actions directory: / - target-branch: dev + target-branch: main schedule: interval: weekly open-pull-requests-limit: 1 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index dce298d1..eec4b0f5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Tests on: push: - branches: [main, dev] + branches: [main] pull_request: - branches: [main, dev] + branches: [main] workflow_dispatch: permissions: diff --git a/AGENTS.md b/AGENTS.md index bc31d03b..fbb21512 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ do not copy them into this file. ## Scope and safe changes -- Branch feature work from the current `dev`; use one branch and worktree per +- Branch feature work from the current `main`; use one branch and worktree per session. Follow [`docs/WORKFLOW.md`](docs/WORKFLOW.md) for branch, PR, review, merge, and release operations. - Do not reset, stash, overwrite, or force-push another session's changes. Do diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 833e8b00..bc64765f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ material must not be moved into the repository. ## Development workflow and canonical references -Use one branch and worktree per session, normally branched from `dev`, and keep +Use one branch and worktree per session, normally branched from `main`, and keep each PR to one behavior, contract, or responsibility change. The complete branch, PR/Issue, review, merge, and release policy is [`docs/WORKFLOW.md`](docs/WORKFLOW.md). Read @@ -239,12 +239,12 @@ Be helpful. Don't be a jerk. AI writing patterns are not moral failings — we'r test-only fix may skip it and say why. Scoring, pattern, benchmark, installer and provider changes also need explicit maintainer approval ([`GOVERNANCE.md`](GOVERNANCE.md)). -2. Branch from `dev` and make the smallest independently reviewable change. +2. Branch from `main` and make the smallest independently reviewable change. 3. Explain the problem, scope, non-goals, public-contract impact, risk, and rollback. Include the relevant test or fixture and its evidence. 4. Keep implementation, regression coverage, and required public documentation together; separate unrelated cleanup, generated output, and release bumps. -5. Open the PR into `dev` with a clear description. Use the release workflow - for the later `dev` → `main` merge; a feature PR is not a release. +5. Open the PR into `main` with a clear description. Use the release workflow + for a later version-preparation PR and tag; a feature PR is not an npm release. 6. Include before/after examples for pattern changes and state any check that was not run or is blocked. Do not treat an unrun or stale check as passing. diff --git a/CONTRIBUTING_KR.md b/CONTRIBUTING_KR.md index 74e947df..85566f97 100644 --- a/CONTRIBUTING_KR.md +++ b/CONTRIBUTING_KR.md @@ -35,7 +35,7 @@ scoped agent 파일은 비공개 자료를 지키는 장치이지 공개 대상 ## 개발 워크플로우와 정본 문서 링크 -세션마다 하나의 브랜치와 worktree를 사용하며, 일반적으로 `dev`에서 +세션마다 하나의 브랜치와 worktree를 사용하며, 일반적으로 `main`에서 분기합니다. 각 PR은 하나의 동작, 계약 또는 책임 변경으로 유지합니다. 브랜치, PR/Issue, review, merge, release의 전체 정책은 [`docs/WORKFLOW.md`](docs/WORKFLOW.md)를 따릅니다. 모듈 소유권과 공개 @@ -201,14 +201,14 @@ metadata 검사로 유지합니다. 각 release 항목에는 짧은 semver ratio ## PR 절차 -1. `dev`에서 branch를 만들고 독립적으로 review할 수 있는 가장 작은 +1. `main`에서 branch를 만들고 독립적으로 review할 수 있는 가장 작은 변경을 합니다. 2. 문제, 범위, 비목표, 공개 계약 영향, 위험, rollback을 설명하고 관련 테스트 또는 fixture와 그 증거를 포함합니다. 3. 구현, 회귀 범위, 필요한 공개 문서를 함께 두되 무관한 정리, 생성물, release bump는 분리합니다. -4. 명확한 설명과 함께 `dev`를 대상으로 PR을 엽니다. 이후 `dev` → `main` - merge에는 release 절차를 사용하며, 기능 PR은 release가 아닙니다. +4. 명확한 설명과 함께 `main`을 대상으로 PR을 엽니다. 이후 버전 준비 PR과 태그에는 release 절차를 사용하며, + 기능 PR 병합은 npm release가 아닙니다. 5. 패턴 변경에는 before/after 예시를 포함하고 실행하지 않았거나 막힌 검사를 명시합니다. 실행하지 않았거나 오래된 검사를 통과한 것으로 취급하지 않습니다. diff --git a/docs/QA.md b/docs/QA.md index 44a7fa1b..9aee00ca 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -161,7 +161,7 @@ Procedure per machine: ```bash git clone https://github.com/devswha/patina.git && cd patina -git switch dev # or the exact SHA being accepted +git switch main # or the exact SHA being accepted npm run smoke:platform # add --skip-install if npm ci already ran ``` diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index ee6f1505..d3999026 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -6,7 +6,7 @@ and humans both follow it. The step-by-step PR checklist lives in statuses live in [`docs/QA.md`](QA.md). - Default branch: `main` -- Integration branch: `dev` +- Integration branch: `main` (short-lived feature branches; no persistent `dev`) - Feature branch prefixes: `bot/*` (agent/automation work), `feat/*` (larger features) - Version-bearing files: `package.json`, `SKILL.md`, `.patina.default.yaml`, the README version badge, `.claude-plugin/plugin.json`, and the CHANGELOG @@ -28,41 +28,37 @@ npm-registry installation smoke; ordinary unit/e2e tests do not install packages ## The branch model +```text +bot/ ──PR + CI + review──▶ main +bot/release-X.Y.Z ──version PR────▶ main ──approved tag──▶ npm ``` -bot/ ──PR──▶ dev ──release PR──▶ main ──▶ publish/deploy - (do the work) (integrate/stage) (release) -``` - -- **`main`** — released/deployed history. Never commit directly to it. -- **`dev`** — integration/staging. Everything converges here first and is verified together before a release. -- **`bot/` / `feat/`** — one independently verifiable and - revertible behavior or contract unit, branched **from `dev`**. - -Every PR carries exactly one such unit, together with its regression test and -the contract documentation needed to understand it. Split out unrelated -cleanup, version changes, and generated-file maintenance unless they are -inseparable from the unit. -`dev` MUST always be at or ahead of `main`. If a hotfix lands directly on -`main`, merge `main` → `dev` immediately; a stale `dev` is the most common way -this workflow rots. +- **`main`** is the integration branch and must stay deployable. Never commit + directly to it. A merged feature is not an npm release; tags identify releases. +- **`bot/` / `feat/`** branches start at the latest `main` and + contain one independently reviewable behavior or contract change, its + regression coverage, and required documentation. +- Each session keeps its own branch and worktree. Concurrent agents do not + require a persistent integration branch besides `main`. +- Vercel may deploy a `main` merge independently of npm publication. Confirm + the project's production-branch setting before merging a web-affecting PR; + keep the deployment ID and rollback target. PR previews validate the candidate. ## Feature workflow ```bash -git switch dev && git pull # start from the latest integration state -git switch -c bot/my-feature # branch off dev -# ...edit, commit in small logical commits... -git push -u origin bot/my-feature # external write: needs explicit authority -gh pr create --base dev # CI + review run here +git fetch origin main +git worktree add ../patina-feature -b bot/my-feature origin/main +# edit and verify in the new worktree +git push -u origin bot/my-feature # external write: needs authority +gh pr create --draft --base main # CI runs before marking Ready ``` -- Commit in small, self-contained commits with clear messages. -- Open the PR as **Draft** while scope and acceptance criteria settle. Run the - cheapest relevant local checks and the normal CI before marking it Ready. -- If the branch grows beyond one behavior unit, split it. -- Opening or merging a PR never grants permission to publish, deploy, change - account settings, or write to another external system. +Run the relevant local checks and CI before marking Ready. Fix review findings +on the same PR, rerun affected checks on its final head, and merge only with +maintainer authority. Split a branch that grows beyond one behavior unit. +Opening or merging a PR never grants permission to publish or change another +external system. ## Parallel work @@ -71,17 +67,17 @@ other's uncommitted changes and interleave commits. Use **git worktrees**: separate folders and branches over one shared `.git`. ```bash -git worktree add ../-featureX -b bot/featureX dev +git worktree add ../-featureX -b bot/featureX origin/main git worktree list git worktree remove ../-featureX ``` 1. **One worktree and one branch per session.** Never run two sessions in the same directory on the same branch. -2. **Branch from the latest `dev`**, and merge `dev` periodically into +2. **Branch from the latest `main`**, and merge `origin/main` periodically into long-running work. 3. **Split scope by files.** Disjoint file sets almost never conflict. -4. **`dev` is the single convergence point.** Resolve conflicts once, when each +4. **`main` is the single convergence point.** Resolve conflicts once, when each branch merges into it. Worktrees isolate files and branches, not external state. Give each session its @@ -135,40 +131,50 @@ accounts, settings, credentials, or production data. Each needs explicit authority that names the actor, channel, scope, and operation. An implementation request, plan approval, or review approval grants none of them. -## Release workflow (`dev` → `main`) +## Release workflow (version PR → tag) ```bash -git switch dev && git pull -# 1) confirm the integrated dev tree and the PRs it includes -# 2) bump every version-bearing file once, in this release change -# 3) run the release checks against the release tree -gh pr create --base main --head dev # release PR (external write) -# 4) on green CI and with merge authority: MERGE (not squash) -# 5) tag or publish only through an approved channel procedure -git switch dev && git merge main # keep dev in sync +git fetch origin main +git worktree add ../patina-release -b bot/release-X.Y.Z origin/main +# update version-bearing files and CHANGELOG once; verify the release tree +git push -u origin bot/release-X.Y.Z # external write +gh pr create --base main # external write +# after CI, review, and authorized merge: tag the exact merged main SHA +# publish only through the approved release.yml channel ``` -- A merge into `dev` does not mean users received a release. It closes an - Issue only when the owner confirms its acceptance criteria; a `Closes #...` - keyword in a `dev` PR is not that confirmation. -- The release PR is the one aggregation exception to the size guidance. It adds - no new behavior, lists the already-reviewed PRs it includes, and carries - release-tree and rollback evidence. -- Version changes are release-only. Feature PRs record their semver impact; - the release PR bumps the version-bearing files once. - `npm run release:sync-plugin-versions` copies the root package version into - `.claude-plugin/plugin.json` and the matching marketplace entry only; - `npm run release:check` still validates every mirror. -- **Merge, do not squash, for `dev` → `main`**, so a release keeps per-feature - history. Squash is the default for a small `feature → dev` PR; merge when a - meaningful commit series is the safer rollback. -- Report delivery per channel (npm, web, container) only after the exact - `main` SHA, artifact or deployment ID, and smoke result are recorded. -- npm publication runs through Trusted Publishing (OIDC) from `release.yml` - ([`docs/integrations/release.md`](integrations/release.md)). It is possible - but never automatic: no session may publish, tag, or deploy on its own - initiative, and each publication needs its own explicit authorization. A - release may be integrated and verified without being published. +- Feature PRs record semver impact; version changes belong in a separate + release-preparation PR. It lists the integrated features, adds no new product + behavior, and records release-tree and rollback evidence. +- `npm run release:sync-plugin-versions` copies the root version into the plugin + and marketplace entry; `npm run release:check` validates every mirror. +- Squash small feature or release-preparation PRs. Keep a meaningful commit + series with a merge when that is safer to revert. There is no `dev` merge-back. +- Close an Issue only when its acceptance criteria are satisfied; merging into + `main` is not evidence that npm or a container was published. +- Report delivery separately for npm, web, and containers using the exact SHA, + artifact/deployment ID, and smoke result. Vercel Git deployment may follow + `main` immediately; npm publication requires an approved tag or manual action. +- npm uses Trusted Publishing (OIDC) from `release.yml`. Every tag/publication + still requires explicit authority; integrating code never authorizes one. + See [`docs/integrations/release.md`](integrations/release.md). + +## Migration from the former dev branch + +Before retiring `dev`, record its tip and confirm it is an ancestor of `main`. +Retarget every open PR, update Dependabot and CI, and stop new work from +branching from `dev`. After the migration PR passes and lands, require `lint`, +`quality`, `browser`, `release-install`, `test (18.1.0)`, `test (20)`, `test (22)`, +and `test (lts/*)` on up-to-date PRs to `main`; enforce the checks for admins, +require PRs and resolved conversations, and forbid force pushes and deletion. +For a single-maintainer repository, do not require an additional human approval +that its sole author cannot supply. The maintainer still reviews the findings. + +Only then delete the remote `dev` ref with branch-deletion authority. Do not +switch or remove another session's worktree. Rollback can recreate `dev` at the +recorded tip and restore the old targets through a reviewed PR; no history +rewrite is needed. A repository config file alone does not change GitHub branch +protection or install a GitHub App; verify those external settings separately. ## External tools and dependency updates @@ -188,10 +194,9 @@ Review dependency updates as behavior changes: lifecycle scripts, runtime impact, lockfile installation, and the relevant contract smoke. Keep at most two general update PRs open; triage security updates separately and never auto-merge them just because a bot opened them. `.github/dependabot.yml` -targets `dev` for npm and github-actions version updates. GitHub raises -security updates against the default branch (`main`); when one lands there, -merge `main` → `dev` immediately. GitHub reads `dependabot.yml` from `main`, -so a change to it takes effect only after it reaches `main`. +targets `main` for npm and github-actions version updates, as do GitHub security +updates. GitHub reads `dependabot.yml` from `main`, so configuration changes +take effect only after they reach `main`. ## Exceptions @@ -206,7 +211,7 @@ new tool, a weaker gate, or a burst of automatic Issues or PRs. - Treat unexpected changes as another session's work. **Never revert, stash, reset, or force-push over changes you did not make.** -- **Before pushing a shared branch** (`dev`/`main`), `git fetch` and confirm the +- **Before pushing a shared branch** (`main`), `git fetch` and confirm the push only *adds* commits (fast-forward or a clean merge), never a history rewrite: `git merge-base --is-ancestor origin/ HEAD`. - Prefer PRs over direct pushes to shared branches so CI and review run. @@ -215,7 +220,7 @@ new tool, a weaker gate, or a burst of automatic Issues or PRs. ## Cleanup Delete a merged branch, local and remote, only with branch-deletion authority. -Keep only `main`, `dev`, and branches still in flight. +Keep only `main` and branches still in flight. ```bash git branch -d bot/my-feature # refuses if not merged @@ -227,9 +232,8 @@ git fetch --prune # drop stale remote-tracking refs | Task | Command | |---|---| -| New feature | `git switch dev && git switch -c bot/x` | -| Parallel session | `git worktree add ../repo-x -b bot/x dev` | -| Open PR into dev | external-write authority → `gh pr create --base dev` | +| New feature | `git fetch origin main && git worktree add ../repo-x -b bot/x origin/main` | +| Parallel session | `git worktree add ../repo-x -b bot/x origin/main` | +| Open PR into main | external-write authority → `gh pr create --base main` | | Release | integrate/verify → version bump → external-write authority → PR to `main` → merge → approved channel step | -| Keep dev synced | `git switch dev && git merge main` | | Clean merged branch | deletion authority → `git branch -d bot/x && git push origin --delete bot/x` | diff --git a/docs/integrations/release.md b/docs/integrations/release.md index c8db4c6a..d2219f3e 100644 --- a/docs/integrations/release.md +++ b/docs/integrations/release.md @@ -13,15 +13,13 @@ The npm publication hold ended with the 8.7.0 release. The npm package version does not establish which version is deployed on the website; verify the production version after each web deployment. -Web deployment normally follows the reviewed `dev` → `main` merge and the existing -Vercel project. The isolated 8.5.2 hotfix starts from released `main`; merge it -back into `dev` immediately. Deployment needs no npm publication or release tag. Keep `dev` in -sync with the resulting `main` history and verify the production version and -rewrite flow after deployment. Production deployments must originate from a -`main` SHA: let the Vercel Git integration build the merge commit, or run -`vercel --prod` from a clean `main` checkout. Do not upload from a `dev` -working tree. Keep the previous production deployment ID as the rollback -target. +Web deployment normally follows a reviewed feature PR into `main` through the +existing Vercel project. A version-preparation PR and npm release tag are separate; +a feature merge may reach the website first. Check the project's production +branch and verify the production version and rewrite flow after deployment. +Production deployments must originate from a `main` SHA: let the Vercel Git +integration build the merge commit, or use an authorized `vercel --prod` from a +clean `main` checkout. Keep the previous production deployment ID for rollback. Release tags start the npm publication job; push a tag only for an intended release. The GitHub Release remains coupled to successful npm publication. diff --git a/docs/operations/README.md b/docs/operations/README.md index 67a3f667..52f7cb33 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -53,7 +53,7 @@ Production deployment follows one contract: approved `main` SHA → same-SHA preview → application smoke → maintainer-approved promotion, keeping the prior deployment ID as the rollback target. Build from `main` through the Vercel Git integration or run `vercel --prod` from a clean `main` checkout; never upload -from `dev`. +from a feature branch. ## Standing decisions diff --git a/docs/operations/rollback-drills.md b/docs/operations/rollback-drills.md index 9bd360b1..e24f1dd5 100644 --- a/docs/operations/rollback-drills.md +++ b/docs/operations/rollback-drills.md @@ -56,7 +56,9 @@ Note: rolled-back builds retain their original env snapshot — re-verify - A CLI redeploy has no `VERCEL_GIT_COMMIT_SHA`, so the pro-monitor stays in its fail-closed 503 until the next git deploy. Any drill that used - `redeploy` therefore ends with a dev -> main merge. + `redeploy` therefore ends with an approved Git deployment from `main`, + following [the current workflow](../WORKFLOW.md). The dated evidence below + retains its original branch names. - The monitor cron fires every 15 minutes and alerts Discord when synthetic checks fail. Watch one full cycle after a drill before calling it recovered. diff --git a/tests/unit/ci-workflow.test.js b/tests/unit/ci-workflow.test.js index d4136122..8e3f9814 100644 --- a/tests/unit/ci-workflow.test.js +++ b/tests/unit/ci-workflow.test.js @@ -55,8 +55,8 @@ test('CI workflow YAML configuration contracts retain protected checks, matrix, const events = workflow.on; const jobs = workflow.jobs; - assert.deepEqual(events.push.branches, ['main', 'dev']); - assert.deepEqual(events.pull_request.branches, ['main', 'dev']); + assert.deepEqual(events.push.branches, ['main']); + assert.deepEqual(events.pull_request.branches, ['main']); for (const [eventName, eventConfig] of Object.entries(events)) { if (!eventConfig || typeof eventConfig !== 'object') continue; assert.equal(eventConfig.paths, undefined, `${eventName} must not add path filters`); diff --git a/tests/unit/maintenance-workflows.test.js b/tests/unit/maintenance-workflows.test.js index 635fffc1..7c42754a 100644 --- a/tests/unit/maintenance-workflows.test.js +++ b/tests/unit/maintenance-workflows.test.js @@ -14,7 +14,7 @@ function readYaml(path) { return yaml.load(readFileSync(path, 'utf8')); } -test('Dependabot routes weekly version updates through dev, bounded and unautomated', () => { +test('Dependabot routes weekly version updates through main, bounded and unautomated', () => { const config = readYaml(DEPENDABOT_PATH); assert.equal(config.version, 2); assert.deepEqual(config.updates.map((update) => update['package-ecosystem']), ['npm', 'github-actions']); @@ -25,7 +25,7 @@ test('Dependabot routes weekly version updates through dev, bounded and unautoma assert.equal(update.directory, '/'); assert.deepEqual(update.schedule, { interval: 'weekly' }); assert.equal(update['open-pull-requests-limit'], 1); - assert.equal(update['target-branch'], 'dev'); + assert.equal(update['target-branch'], 'main'); assert.equal(update['rebase-strategy'], undefined); } From c8d4d9507f8e0d894eac18481abab17b2190a330 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:24:29 +0900 Subject: [PATCH 06/10] chore: configure advisory CodeRabbit PR reviews --- .coderabbit.yaml | 78 ++++++++++++++++++++++++++++++++ .github/PULL_REQUEST_TEMPLATE.md | 1 + docs/WORKFLOW.md | 35 ++++++++++---- 3 files changed, 105 insertions(+), 9 deletions(-) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 00000000..a98851f3 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,78 @@ +# yaml-language-server: $schema=https://www.coderabbit.ai/integrations/schema.v2.json +language: ko-KR +early_access: false +tone_instructions: "Report actionable findings with a concrete trigger, impact, and file location. Separate verified defects from uncertainty. Avoid praise and cosmetic churn." + +reviews: + profile: chill + request_changes_workflow: false + high_level_summary_in_walkthrough: true + review_status: true + review_progress: true + fail_commit_status: true + collapse_walkthrough: true + sequence_diagrams: false + poem: false + in_progress_fortune: false + auto_apply_labels: false + auto_assign_reviewers: false + auto_review: + enabled: true + drafts: false + auto_incremental_review: true + # Initial review plus at most two automatic follow-up reviews. + auto_pause_after_reviewed_commits: 3 + path_filters: + - "!docs/benchmarks/*.json" + - "!assets/**" + path_instructions: + - path: "src/features/**" + instructions: "Enforce the deterministic analysis contract in AGENTS.md and docs/ARCHITECTURE.md. Check transitive model/network/secret access, including global fetch and process.env. Treat scoring changes as behavior changes requiring regression evidence." + - path: "{src,bin,api}/**" + instructions: "Check exact-output verification, numbers/entities/polarity/modality/causation, cancellation cleanup, authentication boundaries, and private data in logs. Follow the canonical contracts; do not propose prompt or threshold tuning as cleanup." + - path: ".github/workflows/**" + instructions: "Check least privilege, required checks, explicit network profiles, source/artifact identity, cancellation, and publication authorization. A skipped, stale, or incomplete check is not a pass." + finishing_touches: + docstrings: + enabled: false + unit_tests: + enabled: false + autofix: + enabled: false + fix_ci: + enabled: false + resolve_merge_conflict: + enabled: false + simplify: + enabled: false + +knowledge_base: + code_guidelines: + enabled: true + filePatterns: + - AGENTS.md + - CONTRIBUTING.md + - docs/WORKFLOW.md + - docs/ARCHITECTURE.md + - docs/QA.md + learnings: + scope: local + issues: + scope: local + pull_requests: + scope: local + automatic_linking_mode: disabled + mcp: + usage: disabled + +chat: + auto_reply: false + art: false + +issue_enrichment: + auto_enrich: + enabled: false + planning: + enabled: false + labeling: + auto_apply_labels: false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 01a24c7c..8e54d2c8 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -14,6 +14,7 @@ Refs #... (or why this low-risk change needs no Issue) ## Verification - Commands run and their results (exit codes): +- Independent review: CodeRabbit reviewed head / findings disposition, or named fallback reviewer: - Checks not run, and why (see [`docs/QA.md`](../docs/QA.md)): ## Release and rollback diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index d3999026..de4ba602 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -51,7 +51,7 @@ git fetch origin main git worktree add ../patina-feature -b bot/my-feature origin/main # edit and verify in the new worktree git push -u origin bot/my-feature # external write: needs authority -gh pr create --draft --base main # CI runs before marking Ready +gh pr create --draft --base main # CI runs; CodeRabbit waits for Ready ``` Run the relevant local checks and CI before marking Ready. Fix review findings @@ -115,14 +115,31 @@ Automatic review or QA retries stop after two; keep the first failure and its reason, and never turn a timeout, authentication failure, or cancellation into a pass. -**Review.** A `bot/*` PR gets one independent, read-only review pass (a -reviewer that did not write the change) plus the full deterministic CI: lint, -unit/e2e, quality, and architecture boundaries. Native Codex GitHub review is -not used in this repository. The `vercel` bot only builds a preview; it reads -no code and is not a review. Record the review verdict and findings in the PR, -fix findings on the same PR, and never run two reviewers against the same -diff. A review never replaces required CI, QA, or the maintainer's merge -decision. +**Review.** CodeRabbit is the default independent PR reviewer. The repository +configuration is `.coderabbit.yaml`; the coding guidelines remain in `AGENTS.md`, +`CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, and `docs/QA.md`. Product `SKILL.md` +is reviewed as product content, never used as repository-development policy. + +CodeRabbit reviews Ready PRs into `main`, with an initial review and at most two +automatic follow-ups before pausing. Resolve actionable findings or record why +a finding does not apply. A progress check alone does not establish approval: +record the reviewed head and disposition of findings. CI remains mandatory. +CodeRabbit is advisory during onboarding; the maintainer evaluates the first +five Ready PRs before deciding whether to require its check. Owner: repository +maintainer; review deadline: 2026-10-07; record the decision in the onboarding PR. +Missing or failed reviews are not passes. Until the app is installed and a +review completes, use one independent read-only reviewer and record that +fallback explicitly. Do not run a second AI reviewer on a diff already +reviewed by CodeRabbit. Native Codex GitHub review is not configured. Vercel +preview checks are not code reviews. Review never grants merge authority. + +Install the CodeRabbit GitHub App for this repository only. Code generation, +autofix, issue planning, automatic labels, and reviewer assignment are disabled +in the repository configuration. App permissions are broader than review mode; +installation requires the account owner's consent. Validate a real Ready PR +before claiming the integration is active. See the official +[GitHub setup](https://docs.coderabbit.ai/platforms/github-com) and +[configuration reference](https://docs.coderabbit.ai/reference/configuration). **External writes** include creating or editing Issues, PRs, comments or labels, pushing or deleting branches, changing protection, merging, tagging, From f33b7e80a6d4d46ca5d743be8d85fb6bc8057f95 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:44:01 +0900 Subject: [PATCH 07/10] docs: remove prescriptive agent and approval workflows --- .coderabbit.yaml | 22 +- .github/ISSUE_TEMPLATE/benchmark_corpus.yml | 17 - .github/ISSUE_TEMPLATE/bug_report.yml | 14 - .../ISSUE_TEMPLATE/calibration_concern.yml | 17 - .github/ISSUE_TEMPLATE/false_positive.yml | 14 - .github/ISSUE_TEMPLATE/feature_request.yml | 14 - .github/ISSUE_TEMPLATE/pattern_proposal.yml | 27 +- .github/ISSUE_TEMPLATE/research_proposal.yml | 18 - .github/PULL_REQUEST_TEMPLATE.md | 22 +- AGENTS.md | 96 +----- CONTRIBUTING.md | 307 ++++++----------- CONTRIBUTING_KR.md | 250 +++++--------- GOVERNANCE.md | 68 +--- docs/ARCHITECTURE.md | 38 +-- docs/QA.md | 309 +++++------------- docs/ROADMAP.md | 13 +- docs/TRANSLATIONESE-KO.md | 64 +--- docs/WORKFLOW.md | 284 ++++------------ docs/integrations/docker.md | 5 +- docs/integrations/release.md | 9 +- docs/operations/README.md | 23 +- docs/operations/pro-launch.md | 244 ++++---------- docs/operations/rollback-drills.md | 38 +-- .../2026-09-14-rhetoric-live-diagnostic.md | 2 +- .../2026-09-14-rhetoric-stage-trace.md | 2 +- ...26-09-15-rhetoric-confirmation-decision.md | 14 +- docs/research/2026-rewrite-efficacy-prereg.md | 4 +- docs/research/README.md | 10 + docs/research/corpus-expansion-plan.md | 67 +--- docs/research/humanization-data-backlog.md | 28 +- ...gpt-miss-review-step1-decision-20260902.md | 3 + .../research/ko-translationese-scholarship.md | 6 +- process/pattern-freshness.md | 128 ++------ tests/unit/issue-forms.test.js | 34 +- 34 files changed, 579 insertions(+), 1632 deletions(-) create mode 100644 docs/research/README.md diff --git a/.coderabbit.yaml b/.coderabbit.yaml index a98851f3..373a1cf6 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -1,7 +1,6 @@ # yaml-language-server: $schema=https://www.coderabbit.ai/integrations/schema.v2.json language: ko-KR early_access: false -tone_instructions: "Report actionable findings with a concrete trigger, impact, and file location. Separate verified defects from uncertainty. Avoid praise and cosmetic churn." reviews: profile: chill @@ -20,18 +19,7 @@ reviews: enabled: true drafts: false auto_incremental_review: true - # Initial review plus at most two automatic follow-up reviews. - auto_pause_after_reviewed_commits: 3 - path_filters: - - "!docs/benchmarks/*.json" - - "!assets/**" - path_instructions: - - path: "src/features/**" - instructions: "Enforce the deterministic analysis contract in AGENTS.md and docs/ARCHITECTURE.md. Check transitive model/network/secret access, including global fetch and process.env. Treat scoring changes as behavior changes requiring regression evidence." - - path: "{src,bin,api}/**" - instructions: "Check exact-output verification, numbers/entities/polarity/modality/causation, cancellation cleanup, authentication boundaries, and private data in logs. Follow the canonical contracts; do not propose prompt or threshold tuning as cleanup." - - path: ".github/workflows/**" - instructions: "Check least privilege, required checks, explicit network profiles, source/artifact identity, cancellation, and publication authorization. A skipped, stale, or incomplete check is not a pass." + auto_pause_after_reviewed_commits: 0 finishing_touches: docstrings: enabled: false @@ -48,13 +36,7 @@ reviews: knowledge_base: code_guidelines: - enabled: true - filePatterns: - - AGENTS.md - - CONTRIBUTING.md - - docs/WORKFLOW.md - - docs/ARCHITECTURE.md - - docs/QA.md + enabled: false learnings: scope: local issues: diff --git a/.github/ISSUE_TEMPLATE/benchmark_corpus.yml b/.github/ISSUE_TEMPLATE/benchmark_corpus.yml index 23a74c3a..a108d157 100644 --- a/.github/ISSUE_TEMPLATE/benchmark_corpus.yml +++ b/.github/ISSUE_TEMPLATE/benchmark_corpus.yml @@ -3,10 +3,6 @@ description: Propose benchmark fixtures, corpus sources, or metric changes title: "benchmark: " labels: [benchmark] body: - - type: markdown - attributes: - value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: kind attributes: @@ -35,16 +31,3 @@ body: description: How should we know this improves patina without raising false positives? validations: required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7c350835..0553ef1a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,6 @@ body: attributes: value: | Thanks for reporting a bug. Please avoid sharing private or confidential text. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: input id: version attributes: @@ -52,16 +51,3 @@ body: label: Actual behavior validations: required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/calibration_concern.yml b/.github/ISSUE_TEMPLATE/calibration_concern.yml index 0e14b80d..55121680 100644 --- a/.github/ISSUE_TEMPLATE/calibration_concern.yml +++ b/.github/ISSUE_TEMPLATE/calibration_concern.yml @@ -3,10 +3,6 @@ description: Report a scoring threshold, false-positive, or false-negative conce title: "calibration: " labels: [benchmark] body: - - type: markdown - attributes: - value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: concern_type attributes: @@ -42,16 +38,3 @@ body: description: Explain what score range or pattern behavior would be more appropriate. validations: required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/false_positive.yml b/.github/ISSUE_TEMPLATE/false_positive.yml index b48eed4f..b7aef078 100644 --- a/.github/ISSUE_TEMPLATE/false_positive.yml +++ b/.github/ISSUE_TEMPLATE/false_positive.yml @@ -9,7 +9,6 @@ body: Thanks for helping calibrate patina. Please share only text you are allowed to publish in a GitHub issue. Good reports include the exact paragraph that fired, the score output, and enough context to tell whether the text should become an allowlist/fixture case. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: language attributes: @@ -95,16 +94,3 @@ body: required: true - label: I understand patina scores are editing signals, not authorship verdicts. required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index d40b2eb8..e1b17681 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -7,7 +7,6 @@ body: attributes: value: | Keep the request focused on the user workflow. Avoid pasting private writing samples. - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: area attributes: @@ -36,16 +35,3 @@ body: description: Optional. Mention workarounds or simpler variants. validations: required: false - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/pattern_proposal.yml b/.github/ISSUE_TEMPLATE/pattern_proposal.yml index 6b8b0c17..b8ca101c 100644 --- a/.github/ISSUE_TEMPLATE/pattern_proposal.yml +++ b/.github/ISSUE_TEMPLATE/pattern_proposal.yml @@ -3,10 +3,6 @@ description: Suggest a new AI-writing pattern or rewrite rule title: "pattern: " labels: [patterns] body: - - type: markdown - attributes: - value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - type: dropdown id: language attributes: @@ -38,17 +34,17 @@ body: - type: textarea id: evaluation_fixture attributes: - label: 50-document evaluation fixture - description: Link to a 25-hot / 25-cold fixture or manifest, or describe the collection plan required before promotion. + label: Evaluation examples or fixture + description: "Optional: link examples, a fixture, or a collection idea." validations: - required: true + required: false - type: textarea id: measurement attributes: label: Precision / recall evidence - description: Paste the precision/recall or hot/cold firing summary when available; otherwise state what still blocks promotion. + description: "Optional: share existing measurements or questions to investigate." validations: - required: true + required: false - type: textarea id: rewrite attributes: @@ -63,16 +59,3 @@ body: description: When would this phrase or structure be legitimate human writing? validations: required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/ISSUE_TEMPLATE/research_proposal.yml b/.github/ISSUE_TEMPLATE/research_proposal.yml index 6dbc0ac0..1ca27265 100644 --- a/.github/ISSUE_TEMPLATE/research_proposal.yml +++ b/.github/ISSUE_TEMPLATE/research_proposal.yml @@ -3,11 +3,6 @@ description: Propose an evaluation, corpus, detector comparison, or methodology title: "research: " labels: [research, benchmark] body: - - type: markdown - attributes: - value: | - See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, acceptance, verification, and ownership. - Cancelled human-evaluation and human-labelled SNS studies require a new explicit owner request; maintenance work does not restart them. - type: dropdown id: research_type attributes: @@ -43,16 +38,3 @@ body: description: What artifact should land in the repo when this is done? validations: required: true - - type: textarea - id: acceptance - attributes: - label: Scope and acceptance - description: See the [Issue/PR workflow](https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md) for scope, non-goals, acceptance, verification, risk, and ownership. - placeholder: | - Scope: - Non-goals: - Acceptance criteria: - Verification and risk: - Owner: maintainer until assigned - validations: - required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 8e54d2c8..d287d379 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,23 +1,7 @@ -## Summary +## Changes -What changes, and why? Policy: [`docs/WORKFLOW.md`](../docs/WORKFLOW.md) - -Refs #... (or why this low-risk change needs no Issue) - -## Scope and risk - -- Contract impact: compatible / intentional change / retirement / none -- Not changed by this PR: -- Size: reviewable lines/files; list generated files, lockfiles and moves - separately. Over 600 lines or 15 files needs a reason and owner approval. +What changed, and why? Link related issues if useful. ## Verification -- Commands run and their results (exit codes): -- Independent review: CodeRabbit reviewed head / findings disposition, or named fallback reviewer: -- Checks not run, and why (see [`docs/QA.md`](../docs/QA.md)): - -## Release and rollback - -- Semver impact (the version bump belongs in the release PR): -- Rollback: +Relevant commands, results, and remaining uncertainties. diff --git a/AGENTS.md b/AGENTS.md index fbb21512..a2dfdd7e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,87 +1,17 @@ -# Repository development rules +# Patina repository map -This is Patina's public development entrypoint. It governs repository changes, -not product behavior, product prompts, or personal agent configuration. Keep -product instructions in `SKILL.md`, `core/`, and the relevant source contract; -do not copy them into this file. +- `src/` contains the CLI, deterministic analysis, rewrite verification, and backends. +- `api/` and `playground/` contain the hosted API and browser UI. +- `SKILL.md`, `core/`, `patterns/`, `document-types/`, and `personas/` contain product assets. +- `tests/` contains unit, CLI, integration, and browser fixtures. +- `scripts/` contains development, benchmark, and release tooling. -## Start here +Development uses `main` and short-lived feature branches. Useful references: -1. Check `git status --short --branch` and identify the branch, base, and files - already changed by another session. -2. Read this file and any applicable scoped rules before editing. Treat - unexpected changes as another contributor's work. -3. Read the relevant source, tests, and canonical documentation before choosing - an implementation. Work only within the requested acceptance criteria. -4. State the intended verification profile and any unavailable checks before - relying on their result. +- [Contributor guide](CONTRIBUTING.md) +- [Git and release commands](docs/WORKFLOW.md) +- [Current architecture](docs/ARCHITECTURE.md) +- [Test commands and coverage](docs/QA.md) -## Scope and safe changes - -- Branch feature work from the current `main`; use one branch and worktree per - session. Follow [`docs/WORKFLOW.md`](docs/WORKFLOW.md) for branch, PR, review, - merge, and release operations. -- Do not reset, stash, overwrite, or force-push another session's changes. Do - not edit shared configuration, credentials, processes, ports, or output - directories outside the assigned worktree without explicit ownership. -- Keep a PR to one understandable behavior, contract, or responsibility change. - Include its regression test and required documentation; separate unrelated - cleanup, generated output, and version bumps. -- Preserve public CLI, API, configuration, installation, and package contracts. - Call intentional changes or retirements out with compatibility handling and - rollback notes. -- Do not change scoring thresholds, rewrite prompts, or research conclusions as - incidental maintenance. Do not revive retired or cancelled work. -- Package version changes are release work: record semver impact during feature - work, then update `package.json` and its mirrors once in the release process. - -## Verification and reporting - -- Select existing focused checks for the changed behavior. Deterministic - analysis under `src/features/` stays LLM-, network-, and key-free. -- Reproduce a behavioral failure before changing an expected value. Never delete - a failing test or skip a safety/contract check to make a change pass. -- Report each check as run, failed, cancelled, stale, or not run. Include the - command, exit code, tested head/base or tree, and artifact path when present; - never describe an unrun check as proof. -- A model-backed, hosted, paid, deployment, or external-service check requires - explicit authorization and an isolated test profile. Local fixtures are not - evidence of live-model quality or production health. - -## Public repository boundary - -- Tracked files are public by default. Commit only redistributable source, - documentation, fixtures, and sanitized evidence with a clear owner and - provenance. -- Keep credentials, tokens, private source text, raw model/review output, - runlogs, personal profiles, and local QA/review workspaces out of Git, issues, - telemetry, package tarballs, and public examples. -- Root `AGENTS.md` is the public development entrypoint. Nested or client- - specific agent files remain private unless a separate public policy explicitly - allows them; `.gitignore` is the guard, not permission to publish. -- Historical plans, research, and benchmark records remain labeled with their - date, conditions, and source revision. Do not silently rewrite historical - results as current claims or expose private inputs behind a summary. -- This repository grants no external publication, registry, deployment, account, - or release rights. Maintainer approval and the documented release workflow are - required for any external write. - -## Code Review Rules - -- Keep the deterministic analysis lane independent from model, network, and - secret access; flag any new reverse dependency into rewrite or transport code. -- Flag meaning-preservation risks: changed numbers, entities, polarity, - modality, causation, or unsupported detail in transformed output. -- Flag accepted status without the required anchor or evidence, including a - failed, cancelled, stale, or inconclusive run presented as passed. -- Flag private text, credentials, raw prompts, or identifying execution data in - logs, telemetry, fixtures, package contents, or public documents. -- Flag duplicated sources of truth, undocumented compatibility breaks, and - changes that weaken rollback, cancellation cleanup, or required checks. - -## Canonical references - -- Contributor entrypoint and document boundaries: [`CONTRIBUTING.md`](CONTRIBUTING.md) -- Branch, PR, Issue, merge, and release policy: [`docs/WORKFLOW.md`](docs/WORKFLOW.md) -- Module responsibilities and public contracts: [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) -- Verification profiles, evidence, and QA isolation: [`docs/QA.md`](docs/QA.md) +`npm test` runs unit and CLI fixtures. `npm run lint` runs static checks. +`npm run test:browser` runs Chromium fixtures. Other commands are in `package.json`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bc64765f..f356e846 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,116 +1,66 @@ # Contributing to Patina -Thanks for considering a contribution. Patina is a pattern-based tool, so the most impactful contributions are usually new patterns, better examples, Document Type policies, or Persona refinements. +Contributions include CLI fixes, detection signals, patterns, examples, +Document Types, Personas, integrations, and documentation. -To submit a pattern, start with the [pattern proposal issue form](.github/ISSUE_TEMPLATE/pattern_proposal.yml). The pack template and PR checklist are in [Adding a New Pattern](#adding-a-new-pattern). A short worked pair is in [pattern of the week](docs/community/pattern-of-the-week.md). +## Finding your way around -## Documentation boundaries +- `src/`, `api/`, and `playground/` contain executable product code. +- `SKILL.md` and `core/` describe the product's text-processing flow. +- `patterns/`, `document-types/`, `personas/`, and `examples/` hold product assets. +- [ARCHITECTURE.md](docs/ARCHITECTURE.md) explains the current module layout. +- [QA.md](docs/QA.md) lists test commands and their coverage. +- [WORKFLOW.md](docs/WORKFLOW.md) provides Git, CI, and release examples. +- `docs/research/` contains dated studies and proposals; `docs/operations/` + contains runbooks and past operating records. -The tracked tree is public by default. Keep these roles distinct: +## Local development -- **Development policy:** `AGENTS.md`, this file, and the linked - `docs/WORKFLOW.md`, `docs/ARCHITECTURE.md`, and `docs/QA.md`. -- **Product documentation and assets:** `README*.md`, `SKILL.md`, `core/`, - `patterns/`, `document-types/`, `personas/`, `examples/`, and user-facing - material under `docs/`. -- **Reviewed operational evidence:** a sanitized, explicitly approved record - may live under `docs/operations/`; it is not a substitute for user docs. -- **Historical evidence:** dated plans, research, and benchmark records remain - labeled with their date, conditions, and source revision. Do not rewrite an - historical result as a current claim. +The development checks use Node 24. The published CLI's minimum Node version +is recorded in `package.json`. -Private runbooks, raw user or model text, review/run logs, credentials, tokens, -personal profiles, and local QA workspaces belong outside the public tree. -`docs/internal/` and other gitignored directories and scoped agent files are -guards for private material, not publication targets. -Never copy a private parent `AGENTS.md` or its contents into this repository. -Do not put private material in issues, telemetry, package tarballs, or examples. - -The root `AGENTS.md` is the public repository-development entrypoint; no -product instructions belong there. This repository grants no external -publication, registry, deployment, account, or release rights. Maintainer -approval and the documented release process are required for every external -write. When moving a public root document, link it from `README.md`; private -material must not be moved into the repository. - -## Development workflow and canonical references - -Use one branch and worktree per session, normally branched from `main`, and keep -each PR to one behavior, contract, or responsibility change. The complete -branch, PR/Issue, review, merge, and release policy is -[`docs/WORKFLOW.md`](docs/WORKFLOW.md). Read -[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for module ownership and public -contracts, and [`docs/QA.md`](docs/QA.md) for verification profiles, evidence, -and isolation. Do not duplicate those documents here. - -## Korean Translation Policy - -Primary user docs should keep a Korean companion when they explain installation, support, contribution, examples, or troubleshooting. The required pairs are: - -- `README.md` → `README_KR.md` -- `CONTRIBUTING.md` → `CONTRIBUTING_KR.md` -- `docs/FAQ.md` → `docs/FAQ_KR.md` -- `docs/AUTHENTICATION.md` → `docs/AUTHENTICATION_KR.md` -- `docs/EXAMPLES.md` → `docs/EXAMPLES_KR.md` - -When a PR changes one of these English files, update the Korean pair in the same PR or explain why the translation can safely lag. Keep commands, paths, config keys, issue numbers, and code fences unchanged unless the source file itself changes them. - -## Adding a New Pattern - -If you are proposing a new tell, open a [pattern proposal](.github/ISSUE_TEMPLATE/pattern_proposal.yml) first. The form asks for language, examples, a rewrite, false-positive risk, and a 50-document evaluation fixture or collection plan. - -1. **Pick the right pack.** Patterns live in `patterns/{lang}-{category}.md`. Categories: content, language, style, structure, communication, filler, plus the score-only `viral-hook` pack. That pack file is the template: copy its frontmatter and a numbered `### N.` section. - -2. **Follow the template.** Each pattern needs: - - Number (next available, e.g. #30) - - Watch words - - Fire condition (when should it trigger?) - - Exclusion condition (when should it NOT trigger?) - - Problem description - - Before/after example - -3. **Add to all languages you can.** We have 4 language packs (ko, en, zh, ja). If you only know one, that's fine — file the PR for that language and note the others need translation. - -4. **Update counts.** After adding a pattern: - - Pack header: increment `patterns:` count - - `docs/PATTERNS.md` and `docs/PATTERNS-{lang}.md`: regenerate the catalog; README `Facts` row: update the total - - SKILL.md description: update total if hardcoded - -5. **Add an example.** If possible, add `examples/{lang}-{number}-success-01.md` and `examples/{lang}-{number}-failure-01.md` (false positive case). - -## Improving an Existing Pattern - -The most common improvement: better before/after examples. The "after" text should preserve the original meaning — not rewrite it into something different. - -Good test: if someone read only the "after" text, would they get the same takeaway as the "before"? If the sentiment flips, the example is bad. +```bash +npm ci +npm test +npm run lint +``` -## Pattern Evaluation Checklist +Focused tests are useful during implementation, and `npm run test:browser` +exercises the playground with Chromium fixtures. Additional commands live in +`package.json` and the [harness guide](docs/HARNESS.md). -Before opening a pattern PR, check: +## Adding a new pattern -- **Fire condition:** would at least 2-3 real AI-generated examples trigger it? -- **Exclusion condition:** can a human-written, domain-appropriate example avoid the hit? -- **Semantic risk:** what facts, numbers, polarity, causation, or domain terms could be damaged by the rewrite? -- **Before/after pair:** does the after version preserve the same claims without merely swapping synonyms? -- **Freshness evidence:** link a 50-document hot/cold fixture, manifest, or collection plan when proposing an emerging model-era tell. -- **Count sync:** pack frontmatter `patterns:` must match numbered `### N.` pattern headings. +Patterns live in `patterns/{lang}-{category}.md`. Categories include content, +language, style, structure, communication, filler, and the score-only viral-hook +pack. Existing pack frontmatter and numbered `### N.` entries show the format: -## False Positive Triage Workflow +- Watch words and a fire condition. +- Exclusions and false-positive examples. +- A description of the editing problem. +- Before/after text with the intended meaning preserved. -False positives are expected, especially for academic, encyclopedic, legal, corporate, or heavily edited prose. To report one: +The language packs are Korean, English, Chinese, and Japanese. A contribution +can start with any one of them. The optional [pattern proposal form](.github/ISSUE_TEMPLATE/pattern_proposal.yml) +and [pattern-of-the-week example](docs/community/pattern-of-the-week.md) can +help organize an idea. -1. Use the false-positive issue template. -2. Include language, genre/register, score/audit excerpt, and the specific pattern that over-fired. -3. Remove private text or replace it with a minimal redistributable excerpt. -4. Suggest whether the fix should be an exclusion rule, lower severity, Document Type `pattern-overrides` change, or benchmark fixture. +Pack frontmatter records `patterns:` counts. The catalog is in +`docs/PATTERNS.md` and `docs/PATTERNS-{lang}.md`; examples live in `examples/`. +Count changes can also affect README and SKILL metadata. -Maintainers should prefer tightening exclusions over deleting patterns outright. +Patterns can be added, revised, reduced, or removed as evidence changes. +[Pattern freshness](process/pattern-freshness.md) describes available tools and +provenance fields. -## Adding Benchmark Fixtures +## False positives and fixtures -Suspect-zone fixtures live under `tests/fixtures/suspect-zones/{lang}/{ai|natural}/`. +A useful false-positive report includes the language, writing context, observed +signal, and a small shareable reproduction. Possible fixes include an exclusion, +a changed detector, a Document Type override, or removal of an unreliable pattern. -Each fixture needs YAML frontmatter: +Suspect-zone fixtures live in `tests/fixtures/suspect-zones/{lang}/{ai|natural}/`. +Their frontmatter describes the fixture and expected measurements: ```yaml --- @@ -119,132 +69,59 @@ language: en class: ai expected_hot: true why_designed_this_way: | - Explain which deterministic signal should fire and why. + Explain which deterministic signal this fixture exercises. expected_metrics: cv_band: low --- ``` -Then run: +`npm run benchmark:report` regenerates `tests/quality/results.json`, +`docs/benchmarks/latest.json`, and `docs/benchmarks/latest.md`. -```bash -npm run benchmark:report -``` +## Detection signals + +The current analyzer in `src/features/` computes signals locally without a +model or network. `src/features/index.js` combines them into paragraph and +document results. [ARCHITECTURE.md](docs/ARCHITECTURE.md) maps the callers and +public outputs; `core/stylometry.md` explains the current scoring behavior. + +`npm run benchmark:signal-impact` compares signal contributions and false +positives. Matched human controls, examples of missed detections, and results +by language/register help evaluate a proposed detector. The same tools can +support experiments with new approaches and revisions to existing ones. -This regenerates `tests/quality/results.json`, `docs/benchmarks/latest.json`, and `docs/benchmarks/latest.md`. - -## Adding a Deterministic Detection Signal - -Patterns (above) are LLM-executed catalog entries. A **detection signal** is -different: a deterministic hot/cold input computed in `src/features/*` and -folded into the per-paragraph hot OR rule (burstiness, MATTR, lexicon density, -the Korean diagnostics composite, the ending-monotony signal, etc.). The -analysis layer stays LLM-free, so a new signal is real engineering with a -calibration bar. Follow this loop: - -1. **Diagnose the miss with evidence.** Find where detection fails on a labeled - manifest, not by intuition. `score_review.trigger_counts` in the scored - manifests shows which signals fire on which rows; `npm run benchmark:signal-impact` - reports each existing signal's marginal catch/FP so you can see the gap. -2. **Find a false-positive-safe discriminator.** Compare AI misses against human - controls **at matched length/register** (a short-text confound is not an AI - tell). The discriminator must separate AI from *the human register that - shares the surface feature* — e.g. plain `-다` AI vs formal-human `-다` needed - a burstiness conjunct, not `-다` alone. -3. **Implement it first-class, never by coupling an advisory payload.** Advisory - signals (`translationese`, `koPostEditese.v1`) must not feed the hot verdict - (see [docs/TRANSLATIONESE-KO.md](docs/TRANSLATIONESE-KO.md)). Add a dedicated - computation in `src/features/stylometry.js`, wire it into the hot OR in - `src/features/index.js`, and add a precision gate (length/count floors) if it - over-fires on short or corner-case text. -4. **Keep runtime surfaces aligned.** A signal is not done until it is consistent across: - `src/features/index.js` / server-side feature callers such as - `src/web-rewrite-stream.js`, `scripts/rebaseline-score.mjs` - `trigger_counts`, the hot-rule prose in `core/stylometry.md` and `SKILL.md`, - and unit tests (including precision guards). The browser playground is a - `playground/chatgpt.js` UI over server-side detection/scoring, not a separate - deterministic-analysis mirror. -5. **Measure with the harness, not by hand.** Run `npm run benchmark:signal-impact` - for the marginal before/after, `npm run benchmark` (the 49-fixture suite must - stay 100% — natural fixtures must not flip hot), and confirm the human-control - false-positive rate stays within the published CI in - `docs/benchmarks/rebaseline-latest.md`. Record the measured numbers in the - changelog. The frozen public claim manifests are refreshed in a separate - rebaseline pass, not in the signal PR. -6. **Record the release impact.** A new detection signal changes hot behavior - and normally warrants a **minor** release (it is additive, not a removal). - Record that impact and the measured catch/FP deltas in the PR, but leave - package and mirror updates to the release process. - -Acceptance bar (mirrors the roadmap's deterministic-feature-expansion criteria): -recall or precision improves on the labeled manifest, the human-control -false-positive rate stays within the published tolerance, and the signal ships -with a documented failure mode plus before/after examples. - -See [docs/HARNESS.md](docs/HARNESS.md) for the full measurement-tool map. - -## Translating Examples - -- Preserve the original semantic anchors: numbers, entities, negation, causation, and modality. -- Do not translate an English AI tell literally if it is not a tell in the target language. -- Add a target-language false-positive note when a phrase is normal in that register. -- Keep examples redistributable; do not paste private user text. - -## Adding a Document Type - -Document Types live in `document-types/{name}.md`. Copy an existing policy -(for example `blog.md`), set `document-type:` to the filename stem, and define -`scope`, `purpose`, `audience`, `structure`, `style`, `avoid`, and -language-scoped `pattern-overrides`. `suppress` is applied deterministically; -`reduce`/`amplify` document policy intent but do not currently change a runtime -weight. Keep Persona voice, casual/professional Register markers, and -verification thresholds out of this axis. - -For reusable voice, create a Persona v2 with `patina persona new`. Persona -frontmatter is validated to reject document policy, Register, and safety fields. - -## Pattern Staleness - -AI writing patterns evolve as models get fine-tuned. Some patterns decay (e.g. "delve" after it became a meme), while new ones emerge. - -How we handle this: -- **Community reporting:** If you notice a pattern that's no longer a reliable signal, open an issue -- **New pattern proposals:** If you spot a new AI tell, file a [pattern proposal](.github/ISSUE_TEMPLATE/pattern_proposal.yml) with 3+ real-world examples and a 50-document evaluation fixture or collection plan -- **Quarterly review:** Maintainers follow [`process/pattern-freshness.md`](process/pattern-freshness.md) for corpus freeze windows, promotion thresholds, and frontmatter metadata -- **Lexicon provenance:** Newly mined or re-mined lexicon entries need `added`, `source`, and `last_validated` provenance before changing shipped behavior; run `npm run lexicon:freshness` to verify sidecars match the shipped entries -- **Version notes:** Each pattern pack has a `version` field. Record its - compatibility impact when patterns change; release preparation updates - package and mirror versions through the documented process. -- **No deletion without replacement:** We don't remove patterns outright; we mark them as `low` severity or move them to `reduce` in a Document Type policy +## Document Types and Personas + +Document Types live in `document-types/{name}.md`. Their frontmatter includes +`document-type`, `scope`, `purpose`, `audience`, `structure`, `style`, `avoid`, +and language-scoped `pattern-overrides`. `suppress` currently affects +deterministic analysis; `reduce`/`amplify` describe policy intent without a +runtime weighting change. + +`patina persona new` creates reusable voice metadata. The current Persona +validator distinguishes voice fields from Document Type, Register, and safety +fields. + +## Translations + +English/Korean document pairs include `README`, `CONTRIBUTING`, `docs/FAQ`, +`docs/AUTHENTICATION`, and `docs/EXAMPLES`. Comparing both versions helps keep +commands and explanations aligned. Pattern translations can use examples +natural to the target language instead of literal translations of an English tell. ## Versioning and releases -Record the expected semver impact in a feature or documentation PR, but do not -bump the package version there. Version changes are release-only: the release -PR updates `package.json` (the source of truth), its documented mirrors, and -the matching `CHANGELOG.md` entry once. Use the checks and version-bearing-file -list in [`docs/WORKFLOW.md`](docs/WORKFLOW.md); `npm run release:check` must -remain the final metadata check. Include a short semver rationale in each -release entry. Pattern-pack and Document Type metadata may have their own -compatibility rules, but must not silently change the package release version. - -## Code of Conduct - -Be helpful. Don't be a jerk. AI writing patterns are not moral failings — we're building a tool, not a tribunal. - -## PR Process - -1. Open or find an Issue first when the change affects user-visible behavior, - a public contract, security, payment, or release; a small docs or - test-only fix may skip it and say why. Scoring, pattern, benchmark, - installer and provider changes also need explicit maintainer approval - ([`GOVERNANCE.md`](GOVERNANCE.md)). -2. Branch from `main` and make the smallest independently reviewable change. -3. Explain the problem, scope, non-goals, public-contract impact, risk, and - rollback. Include the relevant test or fixture and its evidence. -4. Keep implementation, regression coverage, and required public documentation - together; separate unrelated cleanup, generated output, and release bumps. -5. Open the PR into `main` with a clear description. Use the release workflow - for a later version-preparation PR and tag; a feature PR is not an npm release. -6. Include before/after examples for pattern changes and state any check that - was not run or is blocked. Do not treat an unrun or stale check as passing. +`package.json` is the package-version source. `npm run release:check` validates +its mirrors and CHANGELOG entry, and `npm run release:sync-plugin-versions` +updates the plugin mirrors. [The release guide](docs/integrations/release.md) +explains artifact creation, npm publication, and container delivery. + +## PR process + +Changes normally reach `main` through a pull request. A helpful description +explains the behavior, reasoning, and relevant checks. Issues are available +for discussion and tracking when useful. Reviews can consider alternatives +and revise the approach as new evidence appears. + +The repository is public. Synthetic examples and sanitized summaries are useful +for discussing private inputs without publishing credentials or personal text. diff --git a/CONTRIBUTING_KR.md b/CONTRIBUTING_KR.md index 85566f97..f2247cf2 100644 --- a/CONTRIBUTING_KR.md +++ b/CONTRIBUTING_KR.md @@ -1,116 +1,65 @@ # Patina에 기여하기 -기여를 검토해 주셔서 감사합니다. Patina는 패턴 기반 도구이므로 가장 큰 도움이 되는 기여는 새 패턴, 더 나은 예시, Document Type 정책, Persona 개선인 경우가 많습니다. +CLI 수정, 감지 신호, 패턴, 예시, Document Type, Persona, 통합 기능, +문서 등 다양한 형태로 기여할 수 있습니다. -패턴을 제출하려면 먼저 [패턴 제안 이슈 폼](.github/ISSUE_TEMPLATE/pattern_proposal.yml)을 여세요. 팩 템플릿과 PR 체크리스트는 [새 패턴 추가](#새-패턴-추가)에 있습니다. 성공/오탐 한 쌍을 짧게 풀어 둔 글은 [pattern of the week](docs/community/pattern-of-the-week.md)에 있습니다. +## 저장소 구성 -## 문서 경계 +- `src/`, `api/`, `playground/`에는 실행되는 제품 코드가 있습니다. +- `SKILL.md`와 `core/`는 제품의 텍스트 처리 흐름을 설명합니다. +- `patterns/`, `document-types/`, `personas/`, `examples/`에는 제품 자산이 있습니다. +- [ARCHITECTURE.md](docs/ARCHITECTURE.md)는 현재 모듈 구조를 설명합니다. +- [QA.md](docs/QA.md)는 테스트 명령과 검증 범위를 정리합니다. +- [WORKFLOW.md](docs/WORKFLOW.md)에는 Git, CI, 릴리스 명령 예시가 있습니다. +- `docs/research/`에는 날짜별 연구와 제안이, `docs/operations/`에는 + 운영 방법과 과거 기록이 있습니다. -추적되는 트리는 기본적으로 공개됩니다. 다음 역할을 구분해 유지합니다. +## 로컬 개발 -- **개발 정책:** `AGENTS.md`, 이 문서, 그리고 연결된 - `docs/WORKFLOW.md`, `docs/ARCHITECTURE.md`, `docs/QA.md`. -- **제품 문서와 자산:** `README*.md`, `SKILL.md`, `core/`, `patterns/`, - `document-types/`, `personas/`, `examples/`, 그리고 `docs/` 아래의 - 사용자용 자료. -- **검토된 운영 증거:** 위생 처리하고 명시적으로 승인한 기록만 - `docs/operations/` 아래에 둘 수 있습니다. 이는 사용자 문서를 대신하지 - 않습니다. -- **역사적 증거:** 날짜가 있는 계획, 연구, benchmark 기록은 날짜, 조건, - 소스 revision을 표시한 채 유지합니다. 역사적 결과를 현재의 주장으로 - 조용히 바꾸지 않습니다. +개발 검사는 Node 24를 사용합니다. 배포된 CLI의 최소 Node 버전은 +`package.json`에 기록돼 있습니다. -비공개 런북, 가공하지 않은 사용자 또는 모델 텍스트, 리뷰/실행 로그, -자격 증명, 토큰, 개인 프로필, 로컬 QA 작업공간은 공개 트리 밖에 둡니다. -`docs/internal/`, `.gjc/`, `.omo/`, `.omc/`, `.insane-review/`와 무시되는 -scoped agent 파일은 비공개 자료를 지키는 장치이지 공개 대상이 아닙니다. -비공개 상위 `AGENTS.md`나 그 내용을 이 저장소에 복사하지 않습니다. 비공개 -자료를 issue, telemetry, package tarball, 예시에 넣지 않습니다. - -루트 `AGENTS.md`는 공개 저장소 개발 진입점이며, 제품 지침은 그곳에 두지 -않습니다. 이 저장소는 외부 공개, registry, 배포, 계정 또는 release 권한을 -부여하지 않습니다. 모든 외부 쓰기에는 유지보수자 승인과 문서화된 release -절차가 필요합니다. 공개 루트 문서를 옮길 때는 `README.md`에서 링크하고, -비공개 자료는 저장소로 옮기지 않습니다. - -## 개발 워크플로우와 정본 문서 링크 - -세션마다 하나의 브랜치와 worktree를 사용하며, 일반적으로 `main`에서 -분기합니다. 각 PR은 하나의 동작, 계약 또는 책임 변경으로 유지합니다. -브랜치, PR/Issue, review, merge, release의 전체 정책은 -[`docs/WORKFLOW.md`](docs/WORKFLOW.md)를 따릅니다. 모듈 소유권과 공개 -계약은 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)에서, 검증 프로필, -증거와 격리는 [`docs/QA.md`](docs/QA.md)에서 확인합니다. 이 문서들의 -내용을 여기 복제하지 않습니다. - -## 한국어 번역 정책 - -설치, 지원, 기여, 예시, 문제 해결을 설명하는 주요 사용자 문서는 한국어 쌍을 유지해야 합니다. 필수 쌍은 다음과 같습니다. - -- `README.md` → `README_KR.md` -- `CONTRIBUTING.md` → `CONTRIBUTING_KR.md` -- `docs/FAQ.md` → `docs/FAQ_KR.md` -- `docs/AUTHENTICATION.md` → `docs/AUTHENTICATION_KR.md` -- `docs/EXAMPLES.md` → `docs/EXAMPLES_KR.md` +```bash +npm ci +npm test +npm run lint +``` -PR에서 위 영어 문서를 바꾸면 같은 PR에서 한국어 쌍도 갱신하거나, 번역이 잠시 뒤처져도 안전한 이유를 설명하세요. 명령어, 경로, 설정 키, 이슈 번호, 코드 펜스는 원문이 바뀌지 않는 한 그대로 둡니다. +구현 중에는 관련 테스트만 실행할 수 있습니다. `npm run test:browser`는 +Chromium fixture로 playground를 검사합니다. 다른 명령은 `package.json`과 +[하네스 안내](docs/HARNESS.md)에 있습니다. ## 새 패턴 추가 -새 tell을 제안한다면 먼저 [패턴 제안 이슈](.github/ISSUE_TEMPLATE/pattern_proposal.yml)를 엽니다. 폼은 언어, 예시, rewrite, 오탐 위험, 50문서 평가 fixture 또는 수집 계획을 받습니다. - -1. **올바른 팩을 고릅니다.** 패턴은 `patterns/{lang}-{category}.md`에 있습니다. 카테고리: content, language, style, structure, communication, filler, 그리고 score 전용 `viral-hook` 팩입니다. 그 팩 파일이 템플릿입니다. frontmatter와 번호가 붙은 `### N.` 섹션을 복사하세요. - -2. **템플릿을 따릅니다.** 각 패턴에는 다음이 필요합니다. - - 번호(다음 번호, 예: #30) - - Watch words - - Fire condition(언제 감지해야 하는가?) - - Exclusion condition(언제 감지하지 않아야 하는가?) - - 문제 설명 - - before/after 예시 - -3. **가능한 언어에 추가합니다.** 현재 언어 팩은 4개(ko, en, zh, ja)입니다. 한 언어만 알아도 괜찮습니다. 해당 언어 PR을 만들고 나머지는 번역이 필요하다고 적어 주세요. - -4. **카운트를 맞춥니다.** 패턴을 추가한 뒤: - - 팩 헤더의 `patterns:` 수를 올립니다. - - `docs/PATTERNS.md`와 `docs/PATTERNS-{lang}.md`의 카탈로그를 재생성하고, README `Facts` 행의 총계를 갱신합니다. - - SKILL.md 설명에 하드코딩된 총계가 있으면 갱신합니다. - -5. **예시를 추가합니다.** 가능하면 `examples/{lang}-{number}-success-01.md`와 `examples/{lang}-{number}-failure-01.md`(오탐 사례)를 추가합니다. - -## 기존 패턴 개선 - -가장 흔한 개선은 더 나은 before/after 예시입니다. "after" 텍스트는 원래 의미를 보존해야 하며, 다른 내용으로 바꾸면 안 됩니다. - -간단한 확인법: 누군가 "after"만 읽어도 "before"와 같은 핵심 내용을 이해할 수 있나요? 감정 방향이 뒤집히면 나쁜 예시입니다. +패턴은 `patterns/{lang}-{category}.md`에 있습니다. 카테고리는 content, +language, style, structure, communication, filler, score 전용 viral-hook입니다. +기존 팩의 frontmatter와 번호가 붙은 `### N.` 항목에서 형식을 볼 수 있습니다. -## 패턴 평가 체크리스트 +- Watch words와 감지 조건 +- 제외 조건과 오탐 예시 +- 편집하려는 문제의 설명 +- 원래 의미를 유지한 before/after 텍스트 -패턴 PR을 열기 전에 확인하세요. +언어 팩은 한국어, 영어, 중국어, 일본어입니다. 한 언어부터 기여할 수 있습니다. +[패턴 제안 폼](.github/ISSUE_TEMPLATE/pattern_proposal.yml)과 +[pattern of the week](docs/community/pattern-of-the-week.md)는 아이디어를 +정리할 때 참고할 수 있습니다. -- **Fire condition:** 실제 AI 생성 예시 2-3개 이상에서 감지될 수 있나요? -- **Exclusion condition:** 사람이 쓴, 해당 장르에 자연스러운 예시가 감지를 피할 수 있나요? -- **Semantic risk:** rewrite가 손상할 수 있는 사실, 숫자, 극성, 인과, 도메인 용어는 무엇인가요? -- **Before/after pair:** after가 단순 동의어 교체가 아니라 같은 주장을 보존하나요? -- **Freshness evidence:** 새 모델 시대의 tell을 제안한다면 50문서 hot/cold fixture, manifest, 또는 수집 계획을 연결했나요? -- **Count sync:** 팩 frontmatter의 `patterns:`가 번호가 붙은 `### N.` 패턴 heading 수와 같아야 합니다. +팩 frontmatter는 `patterns:` 수를 기록합니다. 카탈로그는 +`docs/PATTERNS.md`와 `docs/PATTERNS-{lang}.md`에, 예시는 `examples/`에 있습니다. +패턴 수가 달라지면 README와 SKILL 메타데이터에도 영향을 줄 수 있습니다. -## 오탐 분류 절차 +근거가 달라지면 패턴을 추가, 수정, 약화하거나 삭제할 수 있습니다. +[패턴 최신성 안내](process/pattern-freshness.md)는 관련 도구와 출처 필드를 설명합니다. -학술, 백과사전식, 법률, 기업, 강하게 편집된 문체에서는 오탐이 생길 수 있습니다. 오탐을 보고하려면: +## 오탐과 fixture -1. false-positive 이슈 템플릿을 사용합니다. -2. 언어, 장르/문체, score/audit excerpt, 과하게 감지된 패턴을 포함합니다. -3. 비공개 텍스트를 제거하거나 재배포 가능한 최소 발췌로 바꿉니다. -4. 수정 방향이 exclusion rule, 낮은 severity, Document Type `pattern-overrides` 변경, benchmark fixture 중 무엇인지 제안합니다. +오탐 보고에는 언어, 글의 맥락, 관찰한 신호, 공유 가능한 작은 재현 사례가 +도움이 됩니다. 제외 조건, 감지 로직, Document Type override를 바꾸거나 +신뢰하기 어려운 패턴을 제거하는 방법 등을 검토할 수 있습니다. -유지보수자는 패턴을 바로 삭제하기보다 exclusion을 좁히는 쪽을 우선해야 합니다. - -## 벤치마크 fixture 추가 - -Suspect-zone fixture는 `tests/fixtures/suspect-zones/{lang}/{ai|natural}/` 아래에 둡니다. - -각 fixture에는 YAML frontmatter가 필요합니다. +Suspect-zone fixture는 `tests/fixtures/suspect-zones/{lang}/{ai|natural}/`에 +있습니다. Frontmatter는 fixture와 예상 측정값을 설명합니다. ```yaml --- @@ -119,96 +68,57 @@ language: en class: ai expected_hot: true why_designed_this_way: | - Explain which deterministic signal should fire and why. + 이 fixture가 검사하는 결정론적 신호를 설명합니다. expected_metrics: cv_band: low --- ``` -그다음 실행합니다. - -```bash -npm run benchmark:report -``` - -이 명령은 `tests/quality/results.json`, `docs/benchmarks/latest.json`, `docs/benchmarks/latest.md`를 다시 생성합니다. - -## 결정론 검출 신호 추가 +`npm run benchmark:report`는 `tests/quality/results.json`, +`docs/benchmarks/latest.json`, `docs/benchmarks/latest.md`를 재생성합니다. -위의 패턴은 LLM이 실행하는 카탈로그 항목입니다. **검출 신호(detection signal)**는 다릅니다 — `src/features/*`에서 계산되어 단락 단위 hot OR 규칙에 합류하는 결정론적 hot/cold 입력입니다(burstiness, MATTR, lexicon density, 한국어 진단 composite, ending-monotony 신호 등). 분석 레이어는 LLM-free를 유지하므로, 새 신호는 보정 기준을 갖춘 실제 엔지니어링입니다. 다음 루프를 따르세요: +## 감지 신호 -1. **근거로 누락을 진단합니다.** 직관이 아니라 라벨드 매니페스트에서 검출이 실패하는 지점을 찾습니다. scored 매니페스트의 `score_review.trigger_counts`가 어떤 신호가 어떤 행에서 발동하는지 보여주고, `npm run benchmark:signal-impact`가 각 기존 신호의 한계 catch/FP를 알려줘 공백을 드러냅니다. -2. **오탐-안전한 판별자를 찾습니다.** AI 누락을 **길이/레지스터가 매칭된** 사람 대조군과 비교합니다(짧은 길이 교란은 AI tell이 아닙니다). 판별자는 *같은 표면 특징을 공유하는 사람 레지스터*와 AI를 갈라야 합니다 — 예: 평서형 `-다` AI vs 격식 사람 `-다`는 `-다` 단독이 아니라 burstiness 결합 조건이 필요했습니다. -3. **advisory 페이로드를 끌어쓰지 말고 1급 신호로 구현합니다.** advisory 신호(`translationese`, `koPostEditese.v1`)는 hot 판정에 들어가면 안 됩니다([docs/TRANSLATIONESE-KO.md](docs/TRANSLATIONESE-KO.md)). `src/features/stylometry.js`에 전용 계산을 추가하고 `src/features/index.js`의 hot OR에 연결하며, 짧은/코너케이스 텍스트에 과발동하면 정밀도 게이트(길이/횟수 하한)를 둡니다. -4. **런타임 표면을 일치시킵니다.** 다음이 일치해야 신호가 완성됩니다: `src/features/index.js` 및 `src/web-rewrite-stream.js` 같은 서버측 feature 호출부, `scripts/rebaseline-score.mjs`의 `trigger_counts`, `core/stylometry.md`·`SKILL.md`의 hot 규칙 산문, 그리고 유닛 테스트(정밀도 가드 포함). 브라우저 playground는 `playground/chatgpt.js` UI이며, 탐지/채점은 별도 브라우저 미러가 아니라 서버측 `src/features/*` 경유로 수행합니다. -5. **손이 아니라 하네스로 측정합니다.** `npm run benchmark:signal-impact`로 한계 before/after를, `npm run benchmark`로 49-fixture(자연 fixture가 hot으로 뒤집히면 안 됨 — 100% 유지)를 확인하고, 사람 대조군 오탐율이 `docs/benchmarks/rebaseline-latest.md`의 공개 CI 내에 머무는지 확인합니다. 측정값은 changelog에 기록합니다. 동결된 공개 claim 매니페스트는 신호 PR이 아니라 별도 rebaseline pass에서 갱신합니다. -6. **release 영향을 기록합니다.** 새 검출 신호는 hot 동작을 바꾸므로 - 일반적으로 **minor** release가 필요합니다(제거가 아닌 추가). PR에 - 해당 영향과 측정한 catch/FP 델타를 기록하되, package와 mirror 갱신은 - release 절차에서 처리합니다. +현재 `src/features/`의 분석기는 모델이나 네트워크 없이 로컬에서 신호를 +계산합니다. `src/features/index.js`는 이를 문단과 문서 결과로 합칩니다. +[ARCHITECTURE.md](docs/ARCHITECTURE.md)는 호출 경로와 공개 출력을, +`core/stylometry.md`는 현재 점수 계산을 설명합니다. -수용 기준(로드맵의 deterministic-feature-expansion 기준과 동일): 라벨드 매니페스트에서 재현율 또는 정밀도가 개선되고, 사람 대조군 오탐율이 공개 허용치 내에 머물며, 신호가 문서화된 실패 모드와 before/after 예시를 함께 제공합니다. +`npm run benchmark:signal-impact`는 신호별 기여와 오탐을 비교합니다. +조건을 맞춘 사람 글, 감지하지 못한 예시, 언어·register별 결과는 새 감지기를 +평가하는 데 도움이 됩니다. 같은 도구로 새로운 접근이나 기존 방식의 수정도 +실험할 수 있습니다. -전체 측정 도구 지도는 [docs/HARNESS.md](docs/HARNESS.md)를 참고하세요. +## Document Type과 Persona -## 예시 번역 +Document Type은 `document-types/{name}.md`에 있습니다. Frontmatter에는 +`document-type`, `scope`, `purpose`, `audience`, `structure`, `style`, `avoid`, +언어별 `pattern-overrides`가 들어갑니다. 현재 `suppress`는 결정론적 분석에 +영향을 주고, `reduce`/`amplify`는 런타임 가중치 변경 없이 정책 의도를 표현합니다. -- 숫자, 엔티티, 부정, 인과, 양태 같은 원문의 semantic anchor를 보존합니다. -- 영어 AI tell을 대상 언어에서 tell이 아닌데 직역하지 않습니다. -- 어떤 표현이 해당 문체에서는 정상이라면 대상 언어의 오탐 메모를 추가합니다. -- 예시는 재배포 가능해야 합니다. 비공개 사용자 텍스트를 붙여 넣지 마세요. +`patina persona new`는 재사용할 수 있는 목소리 메타데이터를 만듭니다. +현재 Persona 검증기는 목소리 필드와 Document Type, Register, 안전성 필드를 +구분합니다. -## Document Type 추가 +## 번역 -Document Type은 `document-types/{name}.md`에 있습니다. 기존 정책(예: -`blog.md`)을 복사하고 `document-type:`을 파일명 stem과 맞춘 뒤 `scope`, -`purpose`, `audience`, `structure`, `style`, `avoid`, 언어별 -`pattern-overrides`를 정의합니다. `suppress`는 결정론적으로 적용되며 -`reduce`/`amplify`는 현재 정책 의도만 기록합니다. Persona 목소리, -casual/professional Register 표지, 검증 하한은 이 축에 넣지 않습니다. +영어·한국어 문서 쌍에는 `README`, `CONTRIBUTING`, `docs/FAQ`, +`docs/AUTHENTICATION`, `docs/EXAMPLES`가 있습니다. 두 버전을 함께 살펴보면 +명령과 설명을 맞추는 데 도움이 됩니다. 패턴은 영어 표현을 그대로 옮기기보다 +대상 언어에서 자연스러운 예시로 설명할 수 있습니다. -재사용 voice는 `patina persona new`로 Persona v2를 만드세요. schema는 -document policy, Register, safety field가 Persona에 들어오면 거부합니다. +## 버전과 릴리스 -## 패턴 노후화 - -AI 문체 패턴은 모델이 미세 조정되면서 바뀝니다. 어떤 패턴은 약해지고(예: "delve"가 밈이 된 뒤), 새 패턴이 나타나기도 합니다. - -처리 방식: -- **커뮤니티 보고:** 더 이상 reliable signal이 아닌 패턴을 발견하면 이슈를 엽니다. -- **새 패턴 제안:** 새 AI tell을 발견하면 [패턴 제안 이슈](.github/ISSUE_TEMPLATE/pattern_proposal.yml)에 실제 예시 3개 이상과 50문서 평가 fixture 또는 수집 계획을 넣어 엽니다. -- **분기별 리뷰:** 유지보수자는 [`process/pattern-freshness.md`](process/pattern-freshness.md)의 corpus freeze window, promotion threshold, frontmatter metadata 규칙을 따릅니다. -- **Lexicon provenance:** 새로 마이닝하거나 다시 마이닝한 lexicon 항목은 동작을 바꾸기 전에 `added`, `source`, `last_validated` provenance를 기록해야 하며, `npm run lexicon:freshness`로 sidecar가 실제 shipped entry와 맞는지 확인합니다. -- **버전 메모:** 각 패턴 팩에는 `version` 필드가 있습니다. 패턴이 바뀌면 - 호환성 영향을 기록하고, package와 mirror 버전 갱신은 문서화된 release - 절차에서 처리합니다. -- **대체 없는 삭제 금지:** 패턴을 바로 제거하지 않습니다. `low` severity로 낮추거나 Document Type에서 `reduce`로 옮깁니다. - -## 버전 관리와 release - -기능 또는 문서 PR에는 예상 semver 영향을 기록하되 package 버전은 올리지 -않습니다. 버전 변경은 release에서만 합니다. release PR에서 `package.json`을 -정본으로 삼아 문서에 명시된 mirror와 해당 `CHANGELOG.md` 항목을 한 번 -갱신합니다. [`docs/WORKFLOW.md`](docs/WORKFLOW.md)의 검사와 -version-bearing 파일 목록을 사용하며, `npm run release:check`를 최종 -metadata 검사로 유지합니다. 각 release 항목에는 짧은 semver rationale을 -포함합니다. 패턴 팩과 Document Type metadata에는 별도의 호환성 규칙이 -있을 수 있지만 package release 버전을 조용히 바꾸면 안 됩니다. - -## 행동 강령 - -도움이 되게 행동하세요. 불필요하게 날을 세우지 마세요. AI 문체 패턴은 도덕적 결함이 아닙니다. 우리는 도구를 만드는 것이지 재판을 여는 것이 아닙니다. +`package.json`이 패키지 버전의 기준입니다. `npm run release:check`는 +연결된 버전 정보와 CHANGELOG를 검사하며, `npm run release:sync-plugin-versions`는 +플러그인 버전 정보를 갱신합니다. [릴리스 안내](docs/integrations/release.md)는 +산출물 생성, npm 공개, 컨테이너 배포를 설명합니다. ## PR 절차 -1. `main`에서 branch를 만들고 독립적으로 review할 수 있는 가장 작은 - 변경을 합니다. -2. 문제, 범위, 비목표, 공개 계약 영향, 위험, rollback을 설명하고 관련 - 테스트 또는 fixture와 그 증거를 포함합니다. -3. 구현, 회귀 범위, 필요한 공개 문서를 함께 두되 무관한 정리, 생성물, - release bump는 분리합니다. -4. 명확한 설명과 함께 `main`을 대상으로 PR을 엽니다. 이후 버전 준비 PR과 태그에는 release 절차를 사용하며, - 기능 PR 병합은 npm release가 아닙니다. -5. 패턴 변경에는 before/after 예시를 포함하고 실행하지 않았거나 막힌 - 검사를 명시합니다. 실행하지 않았거나 오래된 검사를 통과한 것으로 - 취급하지 않습니다. +변경은 보통 PR을 통해 `main`에 반영합니다. 동작, 판단 근거, 관련 검사 결과를 +설명하면 검토에 도움이 됩니다. 논의나 추적이 필요할 때 Issue를 사용할 수 +있습니다. 리뷰 중 다른 대안을 검토하거나 새로운 근거에 따라 접근을 바꿀 수 있습니다. + +저장소는 공개돼 있습니다. 합성 예시와 정리된 요약을 사용하면 자격 증명이나 +개인 텍스트를 공개하지 않고도 비공개 입력의 문제를 논의할 수 있습니다. diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 20c6f8f5..081e22f1 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -1,60 +1,14 @@ -# Governance +# Project maintenance -Patina uses lightweight maintainer governance until the contributor base grows. +Patina is maintained through GitHub issues and pull requests. The maintainer +sets project priorities and coordinates releases; contributors can propose +changes and discuss alternatives in the same repository. -## Decision model +[CONTRIBUTING.md](CONTRIBUTING.md) describes the code and content layout. +[docs/WORKFLOW.md](docs/WORKFLOW.md) lists common Git and release operations. +GitHub repository settings contain the current branch protection and access +configuration. -- Routine docs, tests, examples, and small CLI fixes can be merged after one maintainer review or an equivalent verified maintainer run. -- Pattern, scoring, benchmark, installer, provider, and release changes need explicit maintainer approval. -- Changes that affect the core skill pipeline should explain the meaning-preservation impact and include before/after examples when practical. - -## Change-proposal flow - -Follow the [PR process in CONTRIBUTING.md](CONTRIBUTING.md#pr-process). Prefer follow-up issues over expanding a PR after review starts. - -## Main branch protection - -`main` is protected. The rule is intentionally small and reproducible: - -- force pushes are blocked; -- branch deletion is blocked; -- pull requests are required before normal merges; -- one approving review is required for non-admin merges; -- required checks must pass before merging, and branches must be up to date: - - `lint` - - `test (18.1.0)` - - `test (20)` - - `test (22)` - - `test (lts/*)` - - `quality` - -Administrators are not included in enforcement for now (`enforce_admins=false`) so the maintainer can recover the repository or unblock automation if a protection rule misconfiguration prevents release work. Admin bypass should be treated as an emergency path, not the normal merge path. - -## Close criteria - -An issue can be closed when the linked PR is merged and the issue's acceptance criteria are met, or when the maintainer records why the request is duplicate, out of scope, not reproducible, or intentionally deferred. Partial fixes should leave a comment naming the remaining work. - -## Triage labels - -Suggested label groups: - -- `bug` — incorrect behavior or broken documented workflow -- `enhancement` — product, CLI, integration, or distribution improvement -- `documentation` — docs-only update -- `patterns` — pattern catalog or rewrite-rule change -- `benchmark` — corpus, metrics, calibration, or quality gate work -- `research` — evaluation methodology or external comparison -- `false-positive` — human prose flagged too strongly -- `good first issue` — bounded and low-risk contributor task -- `help wanted` — useful but not on the immediate maintainer path -- `ecosystem` — package, dataset, marketplace, or third-party surface work -- `integration` — editor, GitHub Action, pre-commit, or app integration -- `distribution` — npm, Docker, releases, install channels -- `governance` — maintainer process, labels, community health -- `discussion` — design or research thread before implementation - -## Safety notes - -- Do not position patina as an AI-detector bypass tool. The accepted framing is auditable editing with meaning-preservation checks. -- Do not include private user text in issues, fixtures, or benchmarks unless the contributor has explicit redistribution rights. -- Prefer small, reversible changes over broad rewrites. +Labels such as `bug`, `enhancement`, `documentation`, `patterns`, `benchmark`, +`research`, `false-positive`, and `integration` help organize related work. +Issues can track a bug, question, proposal, or a larger task across PRs. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 19544246..9a834113 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,9 +1,8 @@ # patina architecture: the two engine lanes patina produces every output through one of two methods, and binds them together -with a single rule. This document is the **canonical contract** for which method -governs each surface, which module belongs to which lane, and the invariants each -lane must uphold. +with a single rule. This document describes the current implementation: which +method serves each surface, where modules live, and how outputs are checked. The v7 boundary also defines three independent rewrite axes: Document Type owns document policy, Persona v2 optionally owns reusable voice, and Register owns @@ -14,9 +13,8 @@ constraints, Persona resolves idiolect and rhythm, and Register resolves only casual/professional markers. No active axis supplies a missing axis. -See also: [`CONTRIBUTING.md`](../CONTRIBUTING.md) (the determinism rule — -"Adding a Deterministic Detection Signal"), [`docs/HARNESS.md`](HARNESS.md) (the -measurement/quality **tooling** map — a different axis), +See also: [`CONTRIBUTING.md`](../CONTRIBUTING.md) ("Detection signals"), +[`docs/HARNESS.md`](HARNESS.md) (the measurement/quality **tooling** map — a different axis), [`docs/GLOSSARY.md`](GLOSSARY.md). --- @@ -57,33 +55,31 @@ table below and [Seams: resolved and remaining](#seams-resolved-and-remaining). --- -## Lane invariants (the contract) +## Current lane responsibilities -**Lane A (Method D) MUST:** -- stay LLM-free, deterministic, network-free, and key-free. This is the hard rule - (CONTRIBUTING.md) on `src/features/*` and the deterministic scoring layer. -- only *measure*; it never emits a meaning-changed rewrite. -- not import or depend on Lane B. The dependency direction **A → B is forbidden**. +**Lane A (Method D):** +- computes locally without models, network, or keys in `src/features/*` and + the deterministic scoring layer. +- measures text; it does not produce rewrites. +- has no dependency on Lane B; `npm run check:architecture` checks this boundary. -**Lane B (Method P) MUST:** -- anchor every shipped output to a Method-D computation (reconcile, backstop, or +**Lane B (Method P):** +- anchors every shipped output to a Method-D computation (reconcile, backstop, or gate). -- enforce global meaning preservation independently of every rewrite axis. +- enforces global meaning preservation independently of every rewrite axis. `--verify` owns the configurable MPS/fidelity floors and retry path; no Document Type, Persona, or Register may weaken them. -- treat Persona v2 as **voice composition only**. It may shape vocabulary, +- treats Persona v2 as **voice composition only**. It may shape vocabulary, explanation habits, rhythm, and other voice targets, but never document policy, register, claims, safety thresholds, or worldview. -- keep its own deterministic assets (`src/features/persona-match.js`, +- keeps its own deterministic assets (`src/features/persona-match.js`, `src/verify.js#deterministicMeaningGuard`) auditable and LLM-free even though they serve Lane B. -- **never add an LLM call into `src/features/*`** — the determinism rule binds the - whole analysis layer, not just the modules that happen to live in Lane A today. **Cross-lane:** -- Lane B MAY consume Lane A measurements. **B → A is allowed and expected**: +- Lane B consumes Lane A measurements. **B → A**: `persona-match` and `buildDocumentSignals` reuse `analyzeText()`. -- The reverse (A → B) is forbidden. +- The current import graph has no reverse (A → B) dependency. --- diff --git a/docs/QA.md b/docs/QA.md index 9aee00ca..2027548a 100644 --- a/docs/QA.md +++ b/docs/QA.md @@ -1,231 +1,86 @@ -# QA contract - -How a verification result is run, reported, and accepted. Branch and release -policy lives in [`docs/WORKFLOW.md`](WORKFLOW.md), tool details in -[`docs/HARNESS.md`](HARNESS.md), and module contracts in -[`docs/ARCHITECTURE.md`](ARCHITECTURE.md). `package.json` is the source of -truth for commands; a profile name below is a label, not a new script. -Profiles run from a source checkout. The npm package ships only the runtime, -not the test and benchmark harness. - -## 1. Command profiles - -Use a command that exists in `package.json`, with the arguments shown in the -record. Do not invent `npm run verify`, a generic browser command, or an -unrecorded wrapper. - -A **focused run of the existing test runner** needs no new package script. For -example, `node --test tests/unit/maintenance-workflows.test.js` narrows an -existing profile. It supplements the applicable full profile and required CI -rather than replacing them, and it is recorded with its exact command and exit -code. A replacement test framework, or a wrapper that hides which runner -executed, is not allowed. - -If no existing command covers the boundary you need, record that and mark the -result `inconclusive` until an approved command exists. - -| Profile | Real command | Boundary | -|---|---|---| -| `unit` | `npm run test:unit` | Unit tests | -| `e2e` | `npm run test:e2e` | End-to-end tests; inspect whether transport is mocked | -| `deterministic` | `npm test` | Unit plus e2e (`node --test`); no registry installation or live backend probes | -| `static` | `npm run lint` | Syntax, ESLint, TypeScript, configured CSpell, and AST import boundaries; spellcheck uses this checkout as its gitignore root | -| `release-install` | `npm run test:release-install` | Explicitly online public-registry install of local root/alias tarballs; separate from unit/e2e | -| `browser-fixture` | `npm run test:browser` | Real Chromium, local fixture transport, and checked-JS browser assertions; Node 24 development profile, not live-model quality | -| `regression` | `npm run benchmark` | Fixed quality fixtures | -| `quality-report` | `npm run benchmark:report` | Existing benchmark report generation | -| `performance` | `npm run benchmark:perf` | Report-only deterministic performance measurement | -| `performance-package-size` | `npm run benchmark:perf -- --costmetrics` | Explicit report-only tarball size, cold-process CLI timing, and warm analyzer measurements; no rebaseline or latency gate | -| `dogfood` | `npm run dogfood` | Configured public-document checks | -| `release-safety` | `npm run release:check` and `npm run check:no-private-assets` | Release metadata and private-asset boundary | -| `live-model` | `npm run quality:live` | Opt-in model-backed quality; approval and budget required | - -CI provisions dependencies before running the profiles. The `browser` job -installs Chromium, then runs local transport fixtures; the `release-install` -job confines registry reads to its isolated runner. Local registry installation -requires explicit authorization. The release workflow also exercises installation -when it builds and verifies the exact publication tarballs. Tests of backend -listing inject status probes; adapter authentication tests use owned fixtures. -No profile here proves live-model quality. - -The private-asset gate checks tracked and packaged paths, including `.env` and `.env.*`, -credentials, key files, `docs/internal/`, and client-specific agent rules. -Only root `AGENTS.md` and the sanitized root `.env.example` are tracking -exceptions. This is a path boundary, not a secret-content scanner; inspect -public fixture contents before committing them. - -Other package scripts may fit the changed boundary (for example, -`benchmark:robustness` or `quality:adversarial-mps`). -Record the exact command and arguments rather than assuming a similarly named -script exists. `benchmark:rebaseline*` and other research commands are not -routine acceptance profiles. - -## 2. Recording results - -Record the repository and PR, the profile, the base and head SHA, the exact -command and arguments, the OS, Node and tool versions, the expected and actual -result, the exit code, and any artifact reference. Mark each run with one -status: - -- **`passed`** — completed, and every stated expectation held. -- **`failed`** — completed, but an expectation or gate did not hold. Keep the - failure output. -- **`inconclusive`** — could not run: a missing tool, login, budget, service, - or artifact. It is not a pass. -- **`stale`** — the head, base, or applicable profile changed after the run. -- **`canceled`** — stopped before a valid completion. Keep partial output and - the reason. -- **`not run`** — the profile does not apply to this change, with the reason - recorded. An applicable profile that cannot run is `inconclusive`, never - `not run`. - -Only `passed` counts as passing. A rerun is a new record and does not erase the -first one, and a comment, rerun, or label never upgrades another status. State -limits plainly: a local fixture run proves nothing about hosted CI, another OS -or Node version, real-browser rendering, provider compatibility, production -health, or model quality that was not exercised. - -## 3. Regression and safety expectations - -For a behavior fix, show **regression-before-fix → fix → regression-after-fix** -with the same fixture and command: - -1. Capture the pre-fix failure (or apply a controlled, documented mutation when - the old revision cannot be checked out). -2. Apply the smallest fix and rerun the unchanged fixture and command. -3. Show the expected and actual values and exit codes. - -A missing pre-fix failure is `inconclusive`, not proof that the fix works. Keep -a small set of **negative fixtures** for malformed input, wrong state, -number/negation/causality changes, missing terminal events, unsafe fallback, -and similar boundaries. A negative fixture must fail when the safety behavior -is deliberately broken and pass with the fix; the presence of a source string -is not a behavior check. - -Do not **rebaseline goldens** to make a failing check pass. Do not change -snapshots, benchmark ranges, scores, or expected values together with an -implementation change unless a separately approved contract change explains -why the old expectation is wrong and keeps a record of it. Never skip, weaken, -or comment out a safety, meaning-preservation, authentication, or publication -check to get a green result. Report existing skips with their reason; a new -skip needs an approved, documented exception and stays non-passing until -restored. - -## 4. Isolation, cancellation, and shared limits - -Run untrusted code in a dedicated worktree or checkout with a test-only home and -configuration. Use unique per-run process, temp, output, cache, and port names, -and never copy a personal home or credentials. Record the child PIDs, ports, -and paths the run owns. On completion or cancellation, clean only resources -carrying that run's ownership token or PID; do not use broad `pkill`, -`git clean`/`reset`, or stale-path deletion that could hit another session. - -A private home, cache, or worktree isolates local files. It does **not** create -a new quota, rate limit, lease, concurrency slot, or service. Respect -service-wide limits and locks, and note when a shared service rather than the -local runner was the bottleneck. Run at most one expensive QA job at a time; -queue it or report it unavailable rather than claiming a pass. A timeout, -child-process leak, occupied slot, or failed next invocation is a failed or -inconclusive resource result, not a successful cancellation. - -The default UI path uses fixed fixtures, mock transport, and DOM/mock-browser -seams. That verifies local state transitions and safety contracts only, not -real-browser rendering or real-model quality. Model-backed profiles (such as -`npm run quality:live`) are separately approved, budgeted runs that record the -provider and model ID, timeout, call count, credential boundary, and -non-determinism. A mock-browser pass is never reported as a real-model pass, -and a live-model result cannot stand in for browser interaction or -deterministic regression tests. - -### Other operating systems - -CI runs Linux only, and the repository publishes no OS support matrix. -`npm run smoke:platform` (`scripts/platform-smoke.mjs`) is the per-OS smoke: on -the machine under test it runs `npm ci`, `patina --version`, offline scoring / -`inspect` / batch output on an input file, output directory and working -directory whose names carry Korean and a space, the owned-process cancellation -and session-isolation tests, `doctor --offline --json`, and the full unit + e2e -suite with files enumerated by the script so no shell glob is involved. It -makes no LLM call. - -Read the lifecycle numbers carefully. On platforms without POSIX process -groups the cancellation/isolation tests **skip**, which is *absent coverage*, -not a pass; the `backend-agy` launch tests also skip on Windows because their fake CLI -requires a POSIX shebang. Antigravity settings tests use an owned home. The -receipt therefore carries failing names and counts, and whoever reads it must -state which promise each skip removes rather than treating a skip total as -platform support. `doctor` exit 1 with a valid report ("no usable backend") is -a legitimate observation and counts as ok; a crash or non-JSON output does not. - -Procedure per machine: +# Testing and verification + +`package.json` lists the available commands. These profiles run from a source +checkout; the npm package contains runtime files, not the test harness. + +## Commands + +| Command | Coverage | +|---|---| +| `npm test` | Unit and CLI fixtures; no registry installation or live backend probes | +| `npm run test:unit` | Unit tests | +| `npm run test:e2e` | CLI and integration-style fixtures under `tests/e2e/` | +| `npm run lint` | Syntax, ESLint, TypeScript, CSpell, and AST architecture checks | +| `npm run test:browser` | Real Chromium with local transport fixtures; Node 24 development profile | +| `npm run test:release-install` | Public-registry installation of local root and alias tarballs | +| `npm run benchmark` | Fixed quality fixtures | +| `npm run benchmark:report` | Benchmark report generation | +| `npm run benchmark:compare` | Detector comparison report generation | +| `npm run benchmark:perf` | Deterministic timing report | +| `npm run benchmark:perf -- --costmetrics` | Tarball size, cold CLI timing, and warm analyzer measurements | +| `npm run dogfood` | Configured public-document checks | +| `npm run release:check` | Release metadata and retired-concept checks | +| `npm run check:no-private-assets` | Tracked and packaged path checks | +| `npm run quality:live` | Model-backed quality; uses configured credentials and provider quota | + +A focused test can be run directly, for example: ```bash -git clone https://github.com/devswha/patina.git && cd patina -git switch main # or the exact SHA being accepted -npm run smoke:platform # add --skip-install if npm ci already ran +node --test tests/unit/verify.test.js ``` -The script writes two files. The **public receipt** -`docs/operations/platform-smoke---.json` holds -allow-listed structured evidence only: exit statuses, parsed counts, failing -test names, sanitized error messages, the names (never values) of key -variables that are set, and paths with the home directory replaced by `~`. -The **private diagnostics** -`docs/internal/platform-smoke-<…>.diagnostics.json` (gitignored) hold the raw -child stdout/stderr tails for the operator; they are never committed or sent. -The script prints `ok`/`FAIL` per step, always writes both files (a setup -failure is recorded as a failed step), and exits 1 when any step failed. A -failed step is evidence, not a reason to rerun until green: read the public -receipt for anything the redaction could have missed, then share it and open -the product finding separately. On Windows use a Node-capable shell -(PowerShell or Git Bash); `install.sh` is POSIX-only and is not part of this -smoke. - -## 5. Flaky and external failures - -Classify a failure as product, test-environment, or external-service before -quarantining it. A limited retry is fine for a clearly transient -infrastructure error, but keep the original failure, command, exit code, -artifact, reason, and retry count. Never report only the final successful -retry. - -A flaky exception names an **owner**, a tracking **Issue**, the first-failure -evidence, the quarantine date, a deadline **seven calendar days or less** away, -and a concrete **restore condition**: either the original failure no longer -reproduces on the original fixture and profile, or an approved equivalent gate -is in place and the original test is restored or removed by a reviewed change. -Do not auto-extend, silently delete, or permanently skip it; at the deadline -the maintainer reopens the decision. Meaning-preservation, authentication, -privacy, and publication safety gates may not be quarantined without an -equivalent check and maintainer approval. - -## 6. Merge readiness - -Before a PR is merge-ready: - -1. Scope, acceptance criteria, non-goals, contract impact, and rollback are - written down. -2. The required deterministic profiles and the relevant regression and negative - fixtures are `passed` on the current head. -3. Every other applicable profile (static, private-asset, release, - cancellation/resource, browser, model) is `passed`, or `not run` with the - reason it does not apply. -4. No required job is failed, stale, inconclusive, or canceled, unless an - allowed non-safety exception has an owner, Issue, deadline, restore - condition, and maintainer decision. -5. Artifacts are redacted: no secret, token, private text, user data, personal - path, or unnecessary raw model output. - -The maintainer makes the merge decision. QA never grants an automatic approval, -merge, release, deployment, or publication, and a failed, stale, canceled, or -inconclusive run blocks the gate it covers until it is rerun or the maintainer -records a decision. - -## 7. Artifacts - -Keep only the logs, fixture IDs or hashes, screenshots, and machine output -needed to reproduce a result. Before publishing, redact API keys, access -tokens, cookies, credentials, private or user text, sensitive URLs and query -strings, environment secrets, personal paths, and identifying model payloads. -Record the artifact hash, byte size, and access boundary. Do not commit per-run -screenshots or long logs; private raw corpus and raw prompts stay private. +Choose or add checks that answer the question raised by the change. Fixtures, +reproductions, benchmarks, browser sessions, and live services provide different +kinds of evidence. A local fixture result describes that fixture; hosted CI, +model quality, and production health have their own observations. + +## Test environment + +CI installs dependencies before running the profiles. The browser job installs +Chromium; the release-install job accesses the public npm registry. Backend +listing tests inject status probes, authentication tests use synthetic files, +and Antigravity launch tests use temporary homes. + +Separate homes, caches, temporary directories, and dynamic ports are useful +for tests that interact with local configuration or processes. Existing helpers +under `tests/helpers/` and the nearby test files provide examples. + +The Redis quota regression uses `PATINA_TEST_REDIS_SERVER` and +`PATINA_TEST_REDIS_CLI`. It skips when these executables are absent. CI's quality +job installs them and checks that the regression executed with zero skips. + +## Results and diagnostics + +Useful result summaries identify the command, tested revision, outcome, and +any relevant skips or environmental limitations. A failure can come from the +product, test fixture, environment, or service; its cause determines the next +step. Reproduction cases and before/after results help explain behavioral fixes. + +The private-asset gate covers paths such as `.env`, `.env.*`, keys, credentials, +`docs/internal/`, and client-specific agent files. Root `AGENTS.md` and the +sanitized root `.env.example` are tracking exceptions. The gate checks paths, +not file contents. Local diagnostics belong in ignored output directories; +shared evidence can use synthetic text and summaries without credentials. + +## Platform smoke + +CI currently runs Linux. `npm run smoke:platform` exercises the local platform: +installation, CLI version, offline scoring, inspect/batch file handling, +process cancellation and isolation, offline doctor, and unit/e2e tests. +It makes no model call. + +```bash +npm run smoke:platform +# If dependencies are already installed: +npm run smoke:platform -- --skip-install +``` + +The script writes a structured receipt to +`docs/operations/platform-smoke---.json` and raw +diagnostics to ignored `docs/internal/`. The receipt includes exit statuses, +test counts, failing names, and sanitized paths. On Windows, cancellation tests +and fake Antigravity launch fixtures have POSIX-related skips. A doctor report +with no usable backend is distinct from a crash or malformed output. + +Windows runs use a Node-capable shell such as PowerShell or Git Bash. +`install.sh` is POSIX-only. Further tooling details are in [HARNESS.md](HARNESS.md). diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 98739e2e..6d349839 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -64,7 +64,7 @@ Do not lead with “bypass AI detectors.” Lead with: - **Benchmark credibility (quality phase 1).** `docs/benchmarks/latest.md` carries Wilson CIs, ROC-AUC / PR-AUC and threshold diagnostics; the register split is `docs/benchmarks/register-stratified-latest.md`; the adversarial MPS gate is `docs/research/adversarial-mps.md`. `katfish-ko-latest` (2026-05-21) is frozen as historical evidence: regenerating it needs the private KatFish inputs, which the owner retired on 2026-09-15, so it is not pending work. - **Try-it-now experience.** Hosted playground at (server-side rewrite, `docs/HTTP-API.md`); brand assets in `assets/brand/` (`docs/BRANDING.md`). -- **Packaging and distribution.** npm `patina-cli` and `patina-humanizer` publish through npm Trusted Publishing (OIDC); every publication still needs separate, explicit external-write authorization. GitHub Releases follow a successful npm publication on tag push (`docs/integrations/release.md`). The public image `ghcr.io/devswha/patina:latest` has its own manual release path (`docs/integrations/docker.md`). The README Quick Start states the current source and npm versions. Homebrew has not started. +- **Packaging and distribution.** npm `patina-cli` and `patina-humanizer` publish through npm Trusted Publishing (OIDC). GitHub Releases follow a successful npm publication on tag push (`docs/integrations/release.md`). The public image `ghcr.io/devswha/patina:latest` has its own manual release path (`docs/integrations/docker.md`). The README Quick Start states the current source and npm versions. Homebrew has not started. - **Integrations.** Claude Code / Codex / Cursor / OpenCode skill install, `devswha/patina-action`, and the pre-commit recipe under `docs/integrations/`. Subagent strict flow: `docs/agents.md`. - **Custom Persona authoring (7.0.0).** Document Type owns genre, purpose, structural conventions, and pattern policy; Persona v2 is optional and owns only reusable voice; Register owns only `casual` or `professional` delivery; meaning preservation and verification are global. `patina persona new|list|show|edit|rm` covers the lifecycle in ko/en/zh/ja. Omitting `--persona` preserves the source voice, and the v7 CLI rejects `--profile`, `--tone`, and `--formality` with migration errors rather than aliases. - **Community health.** Issue forms for bugs, features, pattern proposals, false positives, benchmark corpora, calibration concerns and research proposals; `SECURITY.md`, `SUPPORT.md`, `CODE_OF_CONDUCT.md`, `GOVERNANCE.md`, `MAINTAINERS.md` and the PR template. @@ -75,7 +75,7 @@ Do not lead with “bypass AI detectors.” Lead with: ### Phase 2 — corpus expansion -Status (2026-09-02): partial. KO/EN reached the 2026 rebaseline gate (`docs/research/2026-rebaseline.md`); ZH/JA public coverage and the edited-AI class are still empty — tracked as steps 2 and 8 of the frozen order in `docs/research/humanization-data-backlog.md`. +Status (2026-09-02): partial. KO/EN reached the 2026 rebaseline gate (`docs/research/2026-rebaseline.md`); ZH/JA public coverage and the edited-AI class are still empty — tracked as steps 2 and 8 of the dated plan in `docs/research/humanization-data-backlog.md`. Goal: reduce synthetic-fixture overfitting. @@ -147,7 +147,7 @@ Acceptance criteria: Later work, not shipped behavior: -- **Corpus-distilled quantitative bands**: a separately approved +- **Corpus-distilled quantitative bands**: a possible `persona new --from-corpus ` path could derive per-metric allow-bands from the user's own corpus using LLM-free stylometry. - **Personalized avoided lexicon**: derive a Persona's avoid list from terms @@ -155,8 +155,7 @@ Later work, not shipped behavior: - **Holdout validation**: reserve part of the user's corpus to verify that a derived voice fingerprint generalizes before offering it. -These larger personalization paths remain gated behind payment stabilization -and separate approval. +These personalization paths are ideas for future development. ## 3. Current state and next actions @@ -186,7 +185,7 @@ lives, so nobody re-triages from a stale snapshot. ### Research programme -- Performance-only order frozen 2026-09-01 in +- Performance-only order recorded 2026-09-01 in `docs/research/humanization-data-backlog.md`. Step 1 (KO GPT-family miss-review manifest) is complete (`docs/research/ko-gpt-miss-review-step1-decision-20260902.md`, @@ -203,7 +202,7 @@ lives, so nobody re-triages from a stale snapshot. `2026-panel-v2-design.md`. Korean program verdict: `ko-confirmatory-verdict-20260901.md`. External literature survey: `humanization-literature-2026-09.md`. -- #159 blinded human panel, formerly step 3 of the frozen order, was cancelled +- #159 blinded human panel, formerly step 3 of that plan, was cancelled without running the panel. Its design is retained as history (`docs/research/human-eval-panel.md`). #158 cross-judge matrix was closed 2026-07-12 as answered by Study 1's cross-family panel agreement diff --git a/docs/TRANSLATIONESE-KO.md b/docs/TRANSLATIONESE-KO.md index a3f8a858..c260e078 100644 --- a/docs/TRANSLATIONESE-KO.md +++ b/docs/TRANSLATIONESE-KO.md @@ -66,56 +66,18 @@ claim, score input, gate input, severity band, percentile, or baseline-derived finding. Audit surfaces may show the payload in a separate editing-hint section only; they must not mix it into deterministic severity rows. -## Phase 4 calibration protocol / approval boundary - -Phase 4 is a calibration and decision-report protocol only. It can measure whether -`translationese` or `koPostEditese.v1` correlates with Korean editing needs, but -it cannot by itself authorize coupling. Any future coupling to score, `hot`, -gates, severity, prompt/rewrite gates, benchmark pass/fail, or authorship -language requires a separate product/spec decision and separately approved -execution plan after the evidence package below is complete. - -Required calibration package: - -- **Corpus strata:** balanced Korean native-human controls, acceptable human - translation/post-edit controls, raw machine-translationese samples, LLM Korean - drafts, post-edited LLM drafts, and patina rewrite before/after pairs across - blog, docs, marketing, academic/professional, forum/community, technical, and - short-form UI/help genres. -- **Labels:** each item needs source/provenance, domain, length bucket, register, - translation/editing status, and blinded human labels for `needs_korean_edit`, - `translationese_present`, `post_editese_present`, and - `meaning_preservation_risk`. Use at least two Korean-proficient reviewers with - adjudication and agreement reporting. -- **Metrics:** report precision/recall/F1 for the labels above, false-positive - rate on native-human and acceptable-human-translation controls, - genre-stratified and short-text false-positive rates, confidence intervals, - and representative wins/failures. If a separately approved offline prompt or - rewrite experiment is proposed, its report format must also include human - preference, MPS/fidelity regression, and edit churn. -- **Ablations:** measure translationese only, `koPostEditese.v1` only, combined - signals, raw counts versus normalized ratios, and lexical/endings/interference - /rhythm groups independently. -- **Decision thresholds:** pre-register the minimum precision, maximum control - false-positive rate, allowed MPS/fidelity regression bound, and minimum - per-stratum sample sizes before looking at holdout results. Thresholds must be - justified by holdout evidence, not by convenience on development examples. -- **Rollback rules for any later approved coupling:** coupled experiments must be - feature-flagged or isolated; if false positives, MPS/fidelity regressions, - browser/Node parity drift, or domain skew exceed the approved bound, disable - the coupled behavior and keep advisory display. -- **Deliverables:** publish a corpus manifest schema, Korean editor labeling - guide, offline experiment script/report format, representative wins/failures - appendix, ADR template, and follow-on approval checklist. The ADR may complete - inside Phase 4 only as advisory-only or reject-coupling. Prompt-context, - rewrite-priority, score/gate, benchmark, or authorship-related options may be - documented as follow-on proposals, but none may proceed without a separate - product/spec decision and separately approved execution plan. -- **No-coupling default:** until a later approval explicitly names a coupling and - cites completed corpus evidence, no `translationese` or `koPostEditese.v1` - metric may feed score, `hot`, gates, severity, z-score, baseline, percentile, - prompt gates, rewrite gates, benchmark pass/fail, or authorship verdicts. +## Calibration experiments +The current `translationese` and `koPostEditese.v1` outputs are advisory. +Possible experiments can compare native-human writing, human translations, +machine translations, LLM drafts, and edited variants across registers. +Human labels for editing need, translationese, post-editese, and meaning risk +can be compared with signal precision/recall, false positives, and uncertainty. +Ablations can separate lexical, endings, interference, and rhythm features. + +Those measurements can inform changes to the implementation. The current +advisory output alone does not establish an authorship classifier or a quality +score; those uses need their own evidence. ## Limitations / next @@ -124,6 +86,4 @@ Required calibration package: - The catalog is intentionally small and conservative; expand with corpus evidence and keep the density gate to protect precision. - `koPostEditese.v1` is not wired into `hot`, scores, gates, severity, z-score, - baselines, percentiles, or benchmark decisions. Keep it advisory unless a - separate post-Phase4 product/spec approval explicitly authorizes a narrowly - scoped coupling. + baselines, percentiles, or benchmark decisions in the current implementation. diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index de4ba602..918dca49 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -1,256 +1,98 @@ -# Branching, PR & Release Workflow +# Development workflow -How work moves from a feature branch to a release in this repository. Agents -and humans both follow it. The step-by-step PR checklist lives in -[`CONTRIBUTING.md`](../CONTRIBUTING.md#pr-process); QA profiles and result -statuses live in [`docs/QA.md`](QA.md). +Patina uses `main` for integration and short-lived branches for changes. +The examples below cover common Git, CI, review, and release operations. -- Default branch: `main` -- Integration branch: `main` (short-lived feature branches; no persistent `dev`) -- Feature branch prefixes: `bot/*` (agent/automation work), `feat/*` (larger features) -- Version-bearing files: `package.json`, `SKILL.md`, `.patina.default.yaml`, - the README version badge, `.claude-plugin/plugin.json`, and the CHANGELOG - entry. `npm run release:check` verifies that they agree. - -## CI - -`.github/workflows/test.yml` grants read-only repository access and bounds each -job to 15 minutes. Pull-request runs share one concurrency group per PR, so a -new commit cancels only the superseded run; pushes and manual dispatches run -independently. Lint and quality run on the CI host's Node 24. The test matrix -runs a Node 18.1.0 smoke check plus full tests on Node 20, 22, and floating -`lts/*`. Branch protection requires the exact `test (lts/*)` name, so the -matrix keeps the floating entry rather than a fixed 24; review fixed coverage -when LTS advances. None of this changes the product engine requirement -(`>=18.1.0`). Browser fixtures run on Node 24 with Chromium and blocked -external browser requests. `release-install` is a separate, explicitly online -npm-registry installation smoke; ordinary unit/e2e tests do not install packages. - -## The branch model - -```text -bot/ ──PR + CI + review──▶ main -bot/release-X.Y.Z ──version PR────▶ main ──approved tag──▶ npm -``` - -- **`main`** is the integration branch and must stay deployable. Never commit - directly to it. A merged feature is not an npm release; tags identify releases. -- **`bot/` / `feat/`** branches start at the latest `main` and - contain one independently reviewable behavior or contract change, its - regression coverage, and required documentation. -- Each session keeps its own branch and worktree. Concurrent agents do not - require a persistent integration branch besides `main`. -- Vercel may deploy a `main` merge independently of npm publication. Confirm - the project's production-branch setting before merging a web-affecting PR; - keep the deployment ID and rollback target. PR previews validate the candidate. - -## Feature workflow +## Branches and pull requests ```bash git fetch origin main -git worktree add ../patina-feature -b bot/my-feature origin/main -# edit and verify in the new worktree -git push -u origin bot/my-feature # external write: needs authority -gh pr create --draft --base main # CI runs; CodeRabbit waits for Ready +git switch -c bot/my-change origin/main +# edit and run the checks relevant to the change +git push -u origin bot/my-change +gh pr create --base main ``` -Run the relevant local checks and CI before marking Ready. Fix review findings -on the same PR, rerun affected checks on its final head, and merge only with -maintainer authority. Split a branch that grows beyond one behavior unit. -Opening or merging a PR never grants permission to publish or change another -external system. - -## Parallel work - -Two sessions sharing **one working directory** or **one branch** clobber each -other's uncommitted changes and interleave commits. Use **git worktrees**: -separate folders and branches over one shared `.git`. +A separate worktree is useful when another session is using the checkout: ```bash -git worktree add ../-featureX -b bot/featureX origin/main -git worktree list -git worktree remove ../-featureX +git worktree add ../patina-change -b bot/my-change origin/main ``` -1. **One worktree and one branch per session.** Never run two sessions in the - same directory on the same branch. -2. **Branch from the latest `main`**, and merge `origin/main` periodically into - long-running work. -3. **Split scope by files.** Disjoint file sets almost never conflict. -4. **`main` is the single convergence point.** Resolve conflicts once, when each - branch merges into it. +Issues can track work that benefits from discussion or a longer history. +PR descriptions explain the resulting behavior and relevant validation. +Scope, commit structure, and review depth depend on the change. -Worktrees isolate files and branches, not external state. Give each session its -own home, `XDG_CONFIG_HOME`, cache, temp/output directories, ports, and -processes where the tools allow it, and use only approved test credentials; -never copy a personal home or login session into a worktree. Shared quotas, -rate limits, and concurrency caps stay shared, so a per-session home must not -be used to bypass them. On exit, clean up only what your session owns (by -ownership token or recorded PID). Never use broad `pkill`, `git clean`, -`git reset`, or config edits on another session's state. The owner serializes -changes to shared files such as `package.json`, lockfiles, and orchestration -modules. +## CI -## Issues, PR size, and review +`.github/workflows/test.yml` runs on pushes and PRs to `main`, plus manual +requests. It has read-only repository permissions and 15-minute job timeouts. +New PR commits cancel superseded runs for the same PR. -**Open an Issue first** for anything that changes user-observable behavior, a -CLI/API/configuration/installation contract, security or privacy, payment, -deployment or release behavior, a risky design choice, or work that needs -several PRs. Search for an existing Issue before creating one, and link it -with `Refs #...`. A one-PR typo, documentation fix, or test-only change may -skip the Issue if the PR says why and still states its acceptance criteria and -rollback. Security reports go through the private channel in -[`SECURITY.md`](../SECURITY.md), not a public Issue. +| Job | Coverage | +|---|---| +| `lint` | Syntax, ESLint, TypeScript, CSpell, and architecture checks on Node 24 | +| `test (18.1.0)` | Minimum-engine syntax and benchmark smoke | +| `test (20)`, `test (22)`, `test (lts/*)` | Unit/e2e fixtures with npm offline | +| `quality` | Release metadata, package paths, benchmark drift, public-doc checks, and Redis regression | +| `browser` | Chromium with local transport fixtures on Node 24 | +| `release-install` | Public-registry installation of local root/alias tarballs on Node 24 | -**PR size.** Aim for 200-400 reviewable lines (additions plus deletions). -Split the unit when the reviewable diff passes 600 lines or 15 files. These -are warnings, not an automatic gate. Report generated output, lockfiles, -renames, and pure moves separately from handwritten changes. A size exception -needs the reason the unit cannot be split, its validation and rollback plan, -and owner approval; a label alone is not approval. +The product engine range is `>=18.1.0`. GitHub branch protection is configured +in repository settings; check names come from the workflow's job IDs and matrix. -**Evidence.** List the commands you ran and their results. When the head or -base changes, rerun the affected checks instead of reusing an old result. -Automatic review or QA retries stop after two; keep the first failure and its -reason, and never turn a timeout, authentication failure, or cancellation into -a pass. +## Reviews -**Review.** CodeRabbit is the default independent PR reviewer. The repository -configuration is `.coderabbit.yaml`; the coding guidelines remain in `AGENTS.md`, -`CONTRIBUTING.md`, `docs/ARCHITECTURE.md`, and `docs/QA.md`. Product `SKILL.md` -is reviewed as product content, never used as repository-development policy. +`.coderabbit.yaml` configures CodeRabbit's automated review of Ready PRs. +The GitHub App connection is managed in GitHub. The configuration uses advisory +reviews; PR checks and review comments show what actually ran and what remains +to investigate. A local config file does not install the App. -CodeRabbit reviews Ready PRs into `main`, with an initial review and at most two -automatic follow-ups before pausing. Resolve actionable findings or record why -a finding does not apply. A progress check alone does not establish approval: -record the reviewed head and disposition of findings. CI remains mandatory. -CodeRabbit is advisory during onboarding; the maintainer evaluates the first -five Ready PRs before deciding whether to require its check. Owner: repository -maintainer; review deadline: 2026-10-07; record the decision in the onboarding PR. -Missing or failed reviews are not passes. Until the app is installed and a -review completes, use one independent read-only reviewer and record that -fallback explicitly. Do not run a second AI reviewer on a diff already -reviewed by CodeRabbit. Native Codex GitHub review is not configured. Vercel -preview checks are not code reviews. Review never grants merge authority. +Other review approaches are available when they help investigate a change. -Install the CodeRabbit GitHub App for this repository only. Code generation, -autofix, issue planning, automatic labels, and reviewer assignment are disabled -in the repository configuration. App permissions are broader than review mode; -installation requires the account owner's consent. Validate a real Ready PR -before claiming the integration is active. See the official -[GitHub setup](https://docs.coderabbit.ai/platforms/github-com) and -[configuration reference](https://docs.coderabbit.ai/reference/configuration). +## Releases and deployment -**External writes** include creating or editing Issues, PRs, comments or -labels, pushing or deleting branches, changing protection, merging, tagging, -releasing, publishing to npm or other registries, deploying, and changing -accounts, settings, credentials, or production data. Each needs explicit -authority that names the actor, channel, scope, and operation. An -implementation request, plan approval, or review approval grants none of them. +Feature integration and package publication are separate operations. The +version source is `package.json`; `npm run release:check` checks its mirrors, +and `npm run release:sync-plugin-versions` updates plugin version mirrors. -## Release workflow (version PR → tag) +A typical release updates the version-bearing files and CHANGELOG in a PR, +then tags the merged revision. `release.yml` publishes npm packages on +`v*.*.*` tags using Trusted Publishing (OIDC). It also supports manual runs: ```bash -git fetch origin main -git worktree add ../patina-release -b bot/release-X.Y.Z origin/main -# update version-bearing files and CHANGELOG once; verify the release tree -git push -u origin bot/release-X.Y.Z # external write -gh pr create --base main # external write -# after CI, review, and authorized merge: tag the exact merged main SHA -# publish only through the approved release.yml channel +gh workflow run release.yml --ref main -f publish=false -f publish_ghcr=false ``` -- Feature PRs record semver impact; version changes belong in a separate - release-preparation PR. It lists the integrated features, adds no new product - behavior, and records release-tree and rollback evidence. -- `npm run release:sync-plugin-versions` copies the root version into the plugin - and marketplace entry; `npm run release:check` validates every mirror. -- Squash small feature or release-preparation PRs. Keep a meaningful commit - series with a merge when that is safer to revert. There is no `dev` merge-back. -- Close an Issue only when its acceptance criteria are satisfied; merging into - `main` is not evidence that npm or a container was published. -- Report delivery separately for npm, web, and containers using the exact SHA, - artifact/deployment ID, and smoke result. Vercel Git deployment may follow - `main` immediately; npm publication requires an approved tag or manual action. -- npm uses Trusted Publishing (OIDC) from `release.yml`. Every tag/publication - still requires explicit authority; integrating code never authorizes one. - See [`docs/integrations/release.md`](integrations/release.md). - -## Migration from the former dev branch - -Before retiring `dev`, record its tip and confirm it is an ancestor of `main`. -Retarget every open PR, update Dependabot and CI, and stop new work from -branching from `dev`. After the migration PR passes and lands, require `lint`, -`quality`, `browser`, `release-install`, `test (18.1.0)`, `test (20)`, `test (22)`, -and `test (lts/*)` on up-to-date PRs to `main`; enforce the checks for admins, -require PRs and resolved conversations, and forbid force pushes and deletion. -For a single-maintainer repository, do not require an additional human approval -that its sole author cannot supply. The maintainer still reviews the findings. - -Only then delete the remote `dev` ref with branch-deletion authority. Do not -switch or remove another session's worktree. Rollback can recreate `dev` at the -recorded tip and restore the old targets through a reviewed PR; no history -rewrite is needed. A repository config file alone does not change GitHub branch -protection or install a GitHub App; verify those external settings separately. - -## External tools and dependency updates - -Treat an external CLI or API as a separate compatibility boundary. Record its -identifier, observed version, the capability or output shape patina needs, the -verification date, and a status: `verified`, `unverified`, `incompatible`, or -`unavailable`. Report authentication failure, quota, timeout, network failure, -and malformed output as what they are, never as a quality pass. +That command builds and verifies release artifacts without publishing them. +The npm, GitHub Release, and container paths are described in +[the release integration](integrations/release.md). -Ordinary PR checks use fixed fixtures. A real tool or model call is a -separately authorized run with an explicit model ID, version, timeout, call -limit, cost bound, and credential path; it must not become a hidden -prerequisite of deterministic CI. Reuse the existing backend retry/timeout -owner rather than adding a second retry layer. +Vercel can deploy a `main` merge independently of npm publication. Its project +settings identify the production branch, and deployment history contains the +source SHA and previous deployments for rollback. -Review dependency updates as behavior changes: lifecycle scripts, runtime -impact, lockfile installation, and the relevant contract smoke. Keep at most -two general update PRs open; triage security updates separately and never -auto-merge them just because a bot opened them. `.github/dependabot.yml` -targets `main` for npm and github-actions version updates, as do GitHub security -updates. GitHub reads `dependabot.yml` from `main`, so configuration changes -take effect only after they reach `main`. +## Retiring dev -## Exceptions +After moving open PRs and automation to `main`, compare the current branch tips: -Every exception (size, flaky test, compatibility gap, unverified tool, or -deferred check) names an owner, a next-review date, the condition that closes -it, and its tracking record. Review it when that condition appears; there is no -fixed review cadence. An exception without an owner or date stays unresolved, -not approved, and a missed review goes to the owner rather than authorizing a -new tool, a weaker gate, or a burst of automatic Issues or PRs. - -## Safety rules (you are not alone in the repo) - -- Treat unexpected changes as another session's work. **Never revert, stash, - reset, or force-push over changes you did not make.** -- **Before pushing a shared branch** (`main`), `git fetch` and confirm the - push only *adds* commits (fast-forward or a clean merge), never a history - rewrite: `git merge-base --is-ancestor origin/ HEAD`. -- Prefer PRs over direct pushes to shared branches so CI and review run. -- Commit or stash before switching branches in a shared working directory. +```bash +git fetch origin main dev +git rev-parse origin/dev +git merge-base --is-ancestor origin/dev origin/main +``` -## Cleanup +The ancestor check identifies whether `dev` contains work absent from `main`. +Once migration is complete, the remote ref can be removed with +`git push origin --delete dev`. A retained tip can recreate it if needed. +This operation does not switch local checkouts or remove worktrees. -Delete a merged branch, local and remote, only with branch-deletion authority. -Keep only `main` and branches still in flight. +## Branch cleanup ```bash -git branch -d bot/my-feature # refuses if not merged -git push origin --delete bot/my-feature # needs deletion authority -git fetch --prune # drop stale remote-tracking refs +git branch -d bot/my-change +git push origin --delete bot/my-change +git fetch --prune ``` -## Quick reference - -| Task | Command | -|---|---| -| New feature | `git fetch origin main && git worktree add ../repo-x -b bot/x origin/main` | -| Parallel session | `git worktree add ../repo-x -b bot/x origin/main` | -| Open PR into main | external-write authority → `gh pr create --base main` | -| Release | integrate/verify → version bump → external-write authority → PR to `main` → merge → approved channel step | -| Clean merged branch | deletion authority → `git branch -d bot/x && git push origin --delete bot/x` | +`git branch -d` checks whether the local branch has been merged. diff --git a/docs/integrations/docker.md b/docs/integrations/docker.md index dc16d6c8..85af292c 100644 --- a/docs/integrations/docker.md +++ b/docs/integrations/docker.md @@ -2,10 +2,9 @@ A public image is published at `ghcr.io/devswha/patina` (tag `latest`, verified 2026-09-02). Publishing is **manual**: the `ghcr` job in `release.yml` runs only -on `workflow_dispatch` with `publish_ghcr=true`, after the same release-ready -authorization as the npm publish. Tag pushes publish npm and the GitHub Release +on `workflow_dispatch` with `publish_ghcr=true`. Tag pushes publish npm and the GitHub Release but do not rebuild the image, so `latest` can lag the npm version. Manual -publication must run from `main`. This authorized path publishes `latest`; +publication is accepted by the workflow from `main` and publishes `latest`; the current workflow does not emit a separate version tag from that branch. ```bash diff --git a/docs/integrations/release.md b/docs/integrations/release.md index d2219f3e..27dc4b44 100644 --- a/docs/integrations/release.md +++ b/docs/integrations/release.md @@ -17,9 +17,9 @@ Web deployment normally follows a reviewed feature PR into `main` through the existing Vercel project. A version-preparation PR and npm release tag are separate; a feature merge may reach the website first. Check the project's production branch and verify the production version and rewrite flow after deployment. -Production deployments must originate from a `main` SHA: let the Vercel Git -integration build the merge commit, or use an authorized `vercel --prod` from a -clean `main` checkout. Keep the previous production deployment ID for rollback. +Vercel can build the merge commit through its Git integration, or a deployment +can be created with `vercel --prod` from a `main` checkout. Deployment history +contains previous production builds for rollback. Release tags start the npm publication job; push a tag only for an intended release. The GitHub Release remains coupled to successful npm publication. @@ -38,8 +38,7 @@ It then builds the real root and alias `.tgz` files once with `scripts/release-artifacts.mjs`. The script records `sourceSHA`, the shared version, each tarball's SHA-256/SHA-512/SRI integrity, and the packed file lists in `release-manifest.json`. A clean fixture installs **both tarballs in -one `npm install` command**, then runs `npm ci` from that generated lockfile -dependencies (currently +one `npm install` command**, then runs `npm ci` from that generated lockfile. Dependencies (currently `js-yaml`/`argparse`) may use the public registry; the lockfile must show `file:` resolutions and matching integrity for both `patina-cli` and `patina-humanizer`, with paths resolving to the two supplied tarballs, so diff --git a/docs/operations/README.md b/docs/operations/README.md index 52f7cb33..88a2a895 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -2,7 +2,7 @@ Runbooks and decision records for the hosted playground and the Pro service: payment, serving engines, cost and margin, secrets, rollback and monitoring. -They are for maintainers. Nothing here is needed to run patina, and the +Dated records describe the conditions of their original run. The directory is excluded from the npm tarball (`package.json` `files`: `!docs/operations/**`). @@ -49,11 +49,9 @@ lane owned by [`tests/unit/release-artifacts.test.js`](../../tests/unit/release-artifacts.test.js), with the procedure in [`docs/integrations/release.md`](../integrations/release.md). -Production deployment follows one contract: approved `main` SHA → same-SHA -preview → application smoke → maintainer-approved promotion, keeping the prior -deployment ID as the rollback target. Build from `main` through the Vercel Git -integration or run `vercel --prod` from a clean `main` checkout; never upload -from a feature branch. +The Vercel Git integration uses the project's configured production branch. +A deployment's source SHA, application smoke result, and previous deployment +ID are useful for checking what changed and selecting a rollback target. ## Standing decisions @@ -65,19 +63,11 @@ from a feature branch. - Records name secrets, never values. Production `PATINA_FREE_API_KEY` and `PATINA_PRO_API_KEY` (Vercel Production, Sensitive) are product-only Gemini keys, rotated 2026-09-04. Research jobs use a separate local key. -- The repository maintainer owns recurring maintenance. Repeated alerts for one - repository/channel/tier/deployment/trigger/window are deduplicated into one - incident record with a next-review date; retries are bounded and do not - create unlimited Issues. Credentials, tokens, raw logs, request text, and - provider responses are never collected in these records. - ### OBS-ALERT receipt dropped (2026-09-14) Owner decision: the first eligible `OBS-ALERT-v1` alert/recovery receipt is `not_planned`, and none will be queued. Cron 200 stands as “monitor is -running.” Do not open Sensitive observability credentials or synthesize an -incident for this item. The 8.1.3 Discord envelope and the log-query repair -stay (`pro-monitor-endpoint-repair-20260904.md`, private). +running.” The 8.1.3 Discord envelope and the log-query repair remain (`pro-monitor-endpoint-repair-20260904.md`, private). The monitor issues no receipts and no longer reads `PATINA_PUBLIC_BASE_URL_SHA256` or `PATINA_VERCEL_LOG_QUERY_URL_SHA256`; both @@ -88,8 +78,7 @@ the active list and the recovery message. Polar Pro checkout opened on production on 2026-08-04 (`live-open-20260804.md`, private). That is the payment system. A later “first paid -sale / order count” check is `not_planned`. Do not query Polar or treat -empty webhook logs as unfinished checkout work. The `/api/polar-webhook` +sale / order count” check was recorded as `not_planned`. The `/api/polar-webhook` purchase counter is gone (the endpoint returns 404), so its registration in the Polar dashboard can be deleted. diff --git a/docs/operations/pro-launch.md b/docs/operations/pro-launch.md index 9449ec99..2384c6c7 100644 --- a/docs/operations/pro-launch.md +++ b/docs/operations/pro-launch.md @@ -1,10 +1,7 @@ # Pro launch runbook -This is the v6.4 launch order for the browser checkout and the hosted Pro service. -It deliberately separates opening **new sales** from serving customers who have -already paid. Do not put payment-provider, monitor, webhook, provider, KV, or -HMAC secrets in this file, source control, the generated browser config, logs, -or support tickets. +This runbook describes browser checkout configuration and the hosted Pro service. +Opening new sales and serving existing customers use separate controls. ## Launch configuration @@ -23,10 +20,8 @@ the inputs; deploy the resulting `playground/launch-config.js` with that release `scripts/checkout-evidence-bindings.mjs` is the only authorization table. Its bindings are deeply frozen; environment values alone can never enable checkout. -Add a real binding only after review of an immutable `PAY-STG`/`PAY-B` record -and a human supplies the exact provider checkout URL (currently the Polar -checkout link). Do not add a placeholder or sample live binding. Invalid -enabled configuration fails generation rather than publishing a partly enabled +Each binding records the provider checkout URL and its `PAY-STG`/`PAY-B` +evidence identifier. Invalid enabled configuration fails generation rather than publishing a partly enabled checkout, and explicit `false` wins over every other input. The generated public shape is `{schemaVersion:1, channel, enabled, @@ -85,198 +80,83 @@ atomically clears the active list; its recovery lease is 1 hour. A missing/malformed deployment binding or deadline expiry returns `503`. -## Evidence - -Create an immutable launch record outside this repository containing: deploy -commit and immutable artifact, UTC operator/time, environment, generated config -(with secrets omitted), owner approvals, health result, and rollback result. - -Evidence identifiers are release controls, not free-form labels: - -- Staging checkout evidence must match `PAY-STG-[A-Za-z0-9][A-Za-z0-9_-]*`. -- Production checkout evidence must match `PAY-B-[A-Za-z0-9][A-Za-z0-9_-]*`. - -Do not reuse an identifier for a different environment or release. A Gate D -approval is recorded in the launch record; it does not change the production -config prefix from `PAY-B-`. - -## Staging - -1. Keep production checkout disabled. -2. After the immutable `PAY-STG-...` record is reviewed and a human supplies the - exact provider checkout URL, add its exact `{channel, evidence, origin, path}` - binding to source control. Then set `PATINA_PRO_CHECKOUT_ENABLED=true`, - `PATINA_DEPLOYMENT_CHANNEL=staging`, that exact URL, and that evidence ID in - the staging deployment environment. -3. Generate and inspect the public config: it must have `schemaVersion: 1`, - `channel: 'staging'`, `enabled: true`, the bound origin/path, and only the - evidence ID. Confirm it has no query string, credential, or secret. -4. Exercise the browser CTA, cancel/back path, unavailable checkout path, and - the post-purchase license entry flow using approved staging access. Verify - that an absent or invalid Authorization header is denied and no raw license - appears in client storage, logs, or KV keys. -5. Record results in the evidence record. On any uncertainty, set checkout back - to disabled and regenerate before proceeding. - -## Gate B - -Gate B authorizes a production checkout candidate, not an unreviewed payment -integration. Require staging evidence — or, for the Polar route, the recorded -`PAY_STG_SUPERSEDED_BY_PRODUCTION_RUNTIME` decision backed by the sandbox -integration record and the production zero-amount runtime evidence -(`PAY-LIVE-20260729-POLAR-ea8385dc-4c9c3f17`) — plus a reviewed immutable -`PAY-B-...` record, -the human-supplied exact production checkout URL (currently the Polar checkout -link) and its reviewed -source-controlled binding, server health/admission checks, rollback owner, -support/cancel path, and telemetry and PII review. - -After Gate B, deploy **production with checkout still disabled first**: -`PATINA_PRO_CHECKOUT_ENABLED=false`. Generate and inspect the config and verify -that it is exactly disabled (`channel: 'disabled'`, `enabled: false`, and null -checkout origin, path, and evidence). Verify existing Pro authorization paths -remain fail-closed when upstream validation, production KV, HMAC, or Pro -provider configuration is unavailable. This production-first deployment proves -the service release before it can accept a new sale. Monitor readiness is part -of it: verify the scheduled cron route and server-only configuration, then -record an authorized cron `200` before Gate D. A monitor configuration failure -leaves checkout disabled; it is not waived by a browser-only health check. - -## Gate D - -Gate D is the final live-open decision after the checkout-disabled production -release is healthy. Reconfirm the deployed artifact, production health and -alerts, browser disabled state, sales-close operator, support/cancel/refund -coverage, and the 10-minute sale-close drill result. Record named approvers and -the UTC decision in the launch evidence record. - -A failed or missing Gate D check means leave checkout disabled. Do not substitute -a dashboard observation, an assumed provider credential, or a presumed receipt for -recorded evidence. - -## Live open - -1. Starting from the approved checkout-disabled production artifact, use the - reviewed source-controlled production binding and set - `PATINA_PRO_CHECKOUT_ENABLED=true`, `PATINA_DEPLOYMENT_CHANNEL=production`, - its exact provider checkout URL, and its approved `PAY-B-...` evidence ID. -2. Generate and deploy the browser config atomically with the approved release. -3. Inspect the deployed config and CTA. It must be production, enabled, and - limited to the bound origin/path; it must not expose secrets or a full URL - with query data. -4. Monitor CTA errors, checkout handoff failures, authorization denial classes, - validation latency/errors, Pro admission failures, and incident alerts. Do - not log license keys or customer-entered payment data. -5. Record live-open UTC time, artifact identity, and operator in the evidence - record. +## Checkout evidence -## Sale close +The generator accepts `PAY-STG-[A-Za-z0-9][A-Za-z0-9_-]*` for staging and +`PAY-B-[A-Za-z0-9][A-Za-z0-9_-]*` for production. It matches the evidence ID, +channel, origin, and path against `scripts/checkout-evidence-bindings.mjs`. +The Polar production binding is documented by +`pay-b-binding-polar-20260729.json` and `pay-live-runtime-polar-20260729.json`. +These identifiers and bindings are runtime inputs. + +## Staging and live configuration -Sale close stops **new checkout starts only**. It does not disable existing paid -service. +A staging deployment uses `PATINA_DEPLOYMENT_CHANNEL=staging`, a matching +`PAY-STG-...` binding, and its checkout URL. Production uses `production` and +its `PAY-B-...` binding. In either case, setting +`PATINA_PRO_CHECKOUT_ENABLED=true` and running `npm run launch-config:generate` +produces the enabled config when all inputs match. -1. Set `PATINA_PRO_CHECKOUT_ENABLED=false` in production and regenerate/deploy - the public config. -2. Verify the deployed config is the disabled shape and the browser no longer - offers a usable checkout CTA. -3. Keep the hosted Pro API, license validation, existing subscriptions/licenses, - and paid-service monitoring running unless a separate service-kill decision - is made. -4. Record the close time, artifact, reason, and verification. +The public config contains `schemaVersion: 1`, channel, enabled state, +checkout origin/path, and evidence ID. Useful browser checks include the CTA, +checkout handoff, cancel/back navigation, unavailable checkout, and license +entry. API checks cover missing/invalid authorization and upstream failures. +Deployment history and source SHAs make a change and its rollback reproducible. -### 10-minute sale-close drill +## Sale close -Before live open and at each release rehearsal, time a drill from the sale-close -instruction to verified disabled checkout. The target is **10 minutes or less**: -set the flag false, regenerate/deploy, confirm the disabled config and browser -CTA, and record start/end UTC timestamps. A drill failure blocks Gate D until -remediated and repeated. +Setting `PATINA_PRO_CHECKOUT_ENABLED=false` and regenerating/deploying the +public config stops new checkout starts. The disabled config has +`channel: 'disabled'`, `enabled: false`, and null checkout fields. Existing +paid API access and subscriptions continue independently. -## Service kill +The historical sale-close exercise targeted ten minutes. Observed timings are +in [rollback-drills.md](rollback-drills.md). -Service kill is a separate incident action: it disables the hosted paid-service -path for existing customers as well as new sales. First close sales using the -sale-close procedure. Then use the approved hosting incident control to stop -Pro traffic or take the service offline, while preserving only the minimum -incident evidence without raw licenses or payment data. Notify support, record -the scope and time, and restore only after an owner approves recovery. +## Service interruption and recovery -Do not describe a sale close as a service kill, and do not service-kill merely -to stop new purchases. +Stopping the hosted Pro service also affects existing customers. Hosting +controls and deployment rollback manage that service independently of the +checkout flag. Recovery checks can exercise application health, license +validation, and monitoring on the restored deployment. ## Fallback and customer copy -When checkout is disabled or unavailable, show a neutral, non-deceptive message: +When new checkout is unavailable: > Pro checkout is temporarily unavailable. Existing Pro access is unchanged. -When the paid service is unavailable after purchase: +When the paid service is unavailable: > Pro service is temporarily unavailable. We are investigating; do not share -your license key in support messages. - -Do not promise purchase status, refunds, receipt delivery, or provider behavior -that has not been verified. Support must collect a minimal non-secret reference -through the approved private channel and never ask customers to paste license -keys into public or ticket text. +> your license key in support messages. ## Cancel, refund, and revoke -Use the payment-provider dashboard only after an authorized operator verifies -the request through the approved support identity process. Record an internal -case reference, operator, UTC time, action, and customer-facing outcome without -storing a raw license or payment credential in application logs. - -Cancellation and refund are provider-side commercial actions; they are not proof -that an entitlement is already revoked. Revoke/disable service access through -the approved provider/entitlement process, then verify the application's next -license validation denies access within its configured cache/propagation bound. -Document the result. Do not assert that a receipt or provider credential exists -unless it has been independently verified for that case. - -## Telemetry and PII - -Collect operational aggregates needed for launch safety: deploy/config version, -checkout CTA/handoff result, status/error class, latency, authorization outcome, -and alert state. Launch and monitor telemetry is aggregate-only. For the approved -v6.4 monitor, G001 browser telemetry must never store raw license subjects or -HMAC-derived per-license identifiers. Exclude raw license keys, Authorization -headers, payment data, checkout URLs containing user data, provider credentials, -and monitor secrets. -Use the versioned [monitor query recipes](queries/pro-launch-v1.md) and -[dashboard/drill procedure](dashboards/pro-launch-v1.md) for G003 telemetry; they are -aggregate-only and do not provision external dashboards. - -## Drills - -Run and record these drills before Gate D: - -- Disabled-default drill: remove/false the checkout flag and verify the exact - disabled public config. -- Invalid-config drill: use an invalid channel, URL, or evidence prefix and - verify generation fails rather than opens checkout. -- Staging drill: prove a `PAY-STG-...` release cannot be represented as a - production checkout. -- Sale-close drill: complete the timed 10-minute procedure above while keeping - existing paid service available. -- Service-kill drill: rehearse the separate escalation and restoration path in - a non-production-safe environment. -- License safety drill: verify missing/invalid Authorization is denied and test - evidence/log output contains no raw key. -- G003 monitor drill: use the versioned - [dashboard/drill procedure](dashboards/pro-launch-v1.md) with the - [query recipes](queries/pro-launch-v1.md); record a real cron result, not - seeded counters, mocked delivery, or injected test results. Injected - tests validate code only and are not live evidence. +Cancellations and refunds are payment-provider operations. Service entitlement +is checked separately by license validation and can have a cache/propagation +delay. Provider results and the application's next validation response describe +which steps have completed. Support records can use a case reference without +raw license or payment credentials. + +## Telemetry and diagnostics + +Launch and monitor telemetry uses aggregate status/error classes, latency, +authorization outcomes, and alert state. Browser telemetry has no raw license +subjects or HMAC-derived per-license identifiers. The +[query recipes](queries/pro-launch-v1.md) and +[dashboard/drill reference](dashboards/pro-launch-v1.md) describe the implemented +G003 data shapes. + +Useful diagnostics include disabled/default configuration, invalid binding +rejection, staging/production separation, sale-close propagation, license +validation, and monitor recovery. Local injected fixtures exercise code; live +observations describe the deployment where they ran. ## Secret rotation -Rotate payment-provider, monitor/webhook, provider, KV, and HMAC secrets in the -secret manager—not in browser configuration or this repository. Use a two-party -or staged rotation where the provider supports overlap: add the replacement, -deploy it, verify health and alert delivery without logging values, revoke the -old secret, and verify again. For a suspected compromise, immediately close new -sales, rotate the affected secret, assess whether a service kill is required, -and record the incident. Rotate HMAC material only with a migration plan that -preserves the intended entitlement/rate-limit behavior or deliberately invalidates -it under an approved incident decision. +Secrets live in the deployment secret manager. Where a provider supports +credential overlap, rotation can add the replacement, deploy and check it, +then revoke the previous value. HMAC rotation can invalidate entitlement or +rate-limit identifiers, so its migration behavior depends on the incident and +service requirements. diff --git a/docs/operations/rollback-drills.md b/docs/operations/rollback-drills.md index e24f1dd5..4ba39528 100644 --- a/docs/operations/rollback-drills.md +++ b/docs/operations/rollback-drills.md @@ -56,7 +56,7 @@ Note: rolled-back builds retain their original env snapshot — re-verify - A CLI redeploy has no `VERCEL_GIT_COMMIT_SHA`, so the pro-monitor stays in its fail-closed 503 until the next git deploy. Any drill that used - `redeploy` therefore ends with an approved Git deployment from `main`, + `redeploy` therefore is followed by a Git deployment from `main`, following [the current workflow](../WORKFLOW.md). The dated evidence below retains its original branch names. - The monitor cron fires every 15 minutes and alerts Discord when synthetic @@ -80,19 +80,13 @@ trigger, a `monitor_recovered` message, and consumption of the linked alert state. A pass is code evidence only; it is not a production incident, deployment, or Discord event. -The source authority is -`src/pro-monitor.js#evaluateProMonitor`; the deployed route, when separately -approved, is `api/pro-monitor.js`. The owner authority is the repository -maintainer, who alone approves a production promotion or rollback and records -the exact source SHA, deployment ID, smoke result, and prior rollback ID. -Do not collect credentials, request text, provider responses, or raw logs for -this fixture. +The calculator is `src/pro-monitor.js#evaluateProMonitor`; the deployed route +is `api/pro-monitor.js`. Source SHA, deployment ID, smoke result, and the prior +deployment ID describe the tested build and rollback target. -## P13a — web deployment binding (human-gated) +## P13a — web deployment binding (2026-09-09 record) -The required sequence is **approved main SHA → preview/deployment built from -that same SHA → application smoke → owner-approved promotion → retained prior -deployment ID for rollback**. No step below authorizes a promotion or rollback. +The following is the evidence available during that investigation. Read-only evidence captured from the existing Vercel account (no secret plaintext/decrypt/log access) is limited to: @@ -108,25 +102,23 @@ plaintext/decrypt/log access) is limited to: - `b9fff3e...` is an ancestor of `origin/main`; the deployed `b9fff3e...` tree and the `d7a4741...` release-merge tree are both `7cd7f924d1b2228a9692b64842b69918beaf2a21`. The source-ref/SHA discrepancy - is therefore not a content mismatch or proven malfunction; the maintainer - must reconcile the `sourceRef=dev` exception before promotion. + did not establish a content mismatch or malfunction in that investigation. - `https://patina.vibetip.help` returned a basic read smoke rendering the `8.6.0` title. This is not application acceptance evidence. - Environment metadata exposed target counts `production=34` and `preview=31` with `decrypt=false`; no values were read or printed. Required-check and - remaining account-setting fields were not exposed and remain **unknown**; - account confirmation is human-blocked. + remaining account-setting fields were not exposed and were **unknown** in + that investigation. The earlier GitHub production deployment record `6347382527` (source `d7a4741ed9f767bd22a39255e10acf159351fb7a`) reported success, but did not -prove an application smoke or Vercel account configuration. Real promotion and -rollback remain unexecuted, unapproved, and human-blocked, so the retained -prior ID above is an inventory fact, not a completed rollback drill. +prove an application smoke or Vercel account configuration. That investigation +did not execute promotion or rollback, so the retained prior ID is an inventory +fact, not a completed rollback drill. The separate npm artifact recovery lane owns its mock partial-registry exercise: [`scripts/release-artifacts.mjs`](../../scripts/release-artifacts.mjs), [`tests/unit/release-artifacts.test.js`](../../tests/unit/release-artifacts.test.js), -and [`docs/integrations/release.md`](../integrations/release.md). Do not -report that lane or this web drill as passed until the parent verification -records its result. Native Codex automation settings (P09) and any unexposed -web account configuration remain unknown. +and [`docs/integrations/release.md`](../integrations/release.md). Native Codex +automation settings (P09) and unexposed web account configuration were not +verified by this investigation. diff --git a/docs/research/2026-09-14-rhetoric-live-diagnostic.md b/docs/research/2026-09-14-rhetoric-live-diagnostic.md index e57252f0..b2eab9ee 100644 --- a/docs/research/2026-09-14-rhetoric-live-diagnostic.md +++ b/docs/research/2026-09-14-rhetoric-live-diagnostic.md @@ -4,7 +4,7 @@ Status: PLAN v2 §7.A reproduction on the current dest rewrite path. No product Tree: `bot/plan-live-diagnostic-20260914` @ `142b2eb5a0db9ad75c8e1efb5f77359c3a6148de` (`origin/dev` at branch creation). Plan source (PLAN): the v2 rewrite-quality plan, §4 and §7.A, archived as [`rewrite-quality-plan-v2-20260909.md`](rewrite-quality-plan-v2-20260909.md). -This note records first-available stage outputs for eight synthetic KO cases from PLAN §2.3. It is a path reproduction, not a quality claim, not a language-wide result, and not permission to ship H-RHETORIC. +This note records first-available stage outputs for eight synthetic KO cases from PLAN §2.3. It is a path reproduction, not a quality claim, not a language-wide result. H-RHETORIC was unchanged by this diagnostic. **Mode:** `first_draft_only`. `--verify` was not used. diff --git a/docs/research/2026-09-14-rhetoric-stage-trace.md b/docs/research/2026-09-14-rhetoric-stage-trace.md index 799724d1..c77aa553 100644 --- a/docs/research/2026-09-14-rhetoric-stage-trace.md +++ b/docs/research/2026-09-14-rhetoric-stage-trace.md @@ -6,7 +6,7 @@ Plan source (PLAN v2): the v2 rewrite-quality plan, archived as [`rewrite-qualit This note answers PLAN v2 §4 before any H-RHETORIC experiment: where would a leftover like the PLAN §2.3 synthetic adverb survive on the current rewrite path, and do existing tools already record stage outputs? -**Do not treat this note as permission to ship a prompt change.** +This note records the inspected prompt path; it did not change the implementation. ## Verdict diff --git a/docs/research/2026-09-15-rhetoric-confirmation-decision.md b/docs/research/2026-09-15-rhetoric-confirmation-decision.md index c8e0ca2a..3064cd63 100644 --- a/docs/research/2026-09-15-rhetoric-confirmation-decision.md +++ b/docs/research/2026-09-15-rhetoric-confirmation-decision.md @@ -44,18 +44,16 @@ evidence gate; ZH/JA remain exploration-only on existing data. discriminating failure in #828 appeared on the longer marketing case, not the short PLAN one-liners — sample-size sensitivity is real. - **Starting the pilot immediately without the quota check.** Rejected by - PLAN §7.B's own pre-flight rule and by the repository rule that paid / - model-backed checks need explicit authorization with a recorded profile. + the study's planned quota and call-budget check. - **Building new experiment infrastructure before reusing existing pieces.** - The pilot must first inventory what #817 (`tests/quality/rhetoric-contract.mjs`), + The plan was to inventory what #817 (`tests/quality/rhetoric-contract.mjs`), the #828 A/B scripts (local-only, never committed), and the existing quality runners already provide. -## Next action +## Next action proposed at the time -Run the §7.B pre-flight check (quota/cap evidence per backend, call budget, -wall-clock estimate), record it in this directory, then execute the pilot -exactly as PLAN §7.B specifies: 24 unique sources (KO 16 / EN 8; T12·C6·N6), +The proposed next step was to check per-backend quota, call budget, and runtime, +then record the result and execute the pilot described in PLAN §7.B: 24 unique sources (KO 16 / EN 8; T12·C6·N6), arms N0/G/P/H on one generation model, two judge families with balanced A/B and pre-registered flips, no retry-until-success. @@ -63,6 +61,4 @@ and pre-registered flips, no retry-until-success. - Not experiment results. No call was made for this record. - Not a relaxation of §8 thresholds. -- Not authorization to spend against any specific backend account — the - pre-flight check produces the evidence for that authorization. - Not a change to `PATINA_RHETORIC_POLICY` behavior or defaults. diff --git a/docs/research/2026-rewrite-efficacy-prereg.md b/docs/research/2026-rewrite-efficacy-prereg.md index 87cb90a9..f68110ae 100644 --- a/docs/research/2026-rewrite-efficacy-prereg.md +++ b/docs/research/2026-rewrite-efficacy-prereg.md @@ -473,7 +473,7 @@ candidate on that list — a pre-rewrite **structure plan step**. ### What ships on the result - All criteria pass → the plan-step mechanism graduates to a **product - proposal** (separate approval; candidate surfaces: a restructure option in + proposal** (candidate surfaces: a restructure option in the rewrite pipeline or a redesigned pro pack v2 that carries the plan contract) citing this registration and the measured CIs. - H-S3a fails or a guard rail is violated → published as-is; next iteration @@ -635,7 +635,7 @@ only. - All criteria pass (stage 1) → a product proposal to fold the constraint block into the production rewrite prompt (minimal and strict modes) for ko, - with the en decision waiting on stage 2; separate approval, citing this + with the en decision waiting on stage 2, citing this registration and the measured CIs. - H-4b-a fails or a guard rail is violated → published as-is; the next candidate is H-4a (deterministic merge/split with seam-only infill). No diff --git a/docs/research/README.md b/docs/research/README.md new file mode 100644 index 00000000..1fcd235b --- /dev/null +++ b/docs/research/README.md @@ -0,0 +1,10 @@ +# Research records + +This directory contains dated studies, experiment designs, and implementation +notes. Sample sizes, frozen inputs, approval states, and execution orders refer +to the study that recorded them. They are historical context rather than a +repository-wide development procedure. + +Current executable behavior is in `src/` and `scripts/`; test and benchmark +commands are listed in [`../QA.md`](../QA.md) and [`../HARNESS.md`](../HARNESS.md). +The records can inform new hypotheses, alternative designs, and follow-up work. diff --git a/docs/research/corpus-expansion-plan.md b/docs/research/corpus-expansion-plan.md index 21ac3289..3134470c 100644 --- a/docs/research/corpus-expansion-plan.md +++ b/docs/research/corpus-expansion-plan.md @@ -1,10 +1,10 @@ # Corpus Expansion Plan — unblocking B2 / B3 / B4 -> **Status note (2026-09-02, index pass):** no corpus was generated from this runbook. Its target matrix is carried by steps 2 (edited-AI intake) and 8 (ZH/JA expansion) of the frozen order in `humanization-data-backlog.md`; the workflow section references agent skills that are no longer installed in this workspace. +> **Status note (2026-09-02, index pass):** no corpus was generated from this runbook. Its target matrix was associated with steps 2 (edited-AI intake) and 8 (ZH/JA expansion) of the dated priorities in `humanization-data-backlog.md`. The obsolete agent-skill workflow has been removed. Status: research runbook, no corpus generated by this document. Written: 2026-06-13. -Scope: a cross-session, skill-driven plan to grow the labeled corpus so the +Original scope: a plan to grow the labeled corpus so the slice (B2), robustness (B3), and language-threshold-calibration (B4) work has enough data to be meaningful. Reuses the existing metadata-first rebaseline pipeline; it does not introduce a new corpus store or a new privacy model. @@ -93,35 +93,10 @@ text → fixture frontmatter; restricted text → public manifest (hash-only). | `lengthBucket` | derived (`lengthBucket()` on body code points) | empty/short/medium/long | derived, never stored | | `edited` | `edit_depth` | none, light, heavy | `unspecified` | -Maintainer decisions still required before B2 slices and B4 calibration can make -claims (these are the four-per-story unblock gates from the approved plan): +## Collection commands -- **B2**: corpus source/manifest with enough records per slice; required - metadata fields + allowed values + where they live; privacy/license per source; - minimum slice-count rule for "reportable vs insufficient". -- **B4**: operating policy (target: TPR@1%/5%FPR · precision floor · recall - floor · or preserve current decisions); data sufficiency (min negatives per - calibrated language, else strict zero-FP diagnostics); approved before/after - deltas by language incl. must-not-regress slices; public messaging wording. - -## Skill-driven, multi-session workflow - -Run these in separate sessions; each writes durable artifacts other sessions -read. - -1. **`/skill:deep-interview`** — turn the open maintainer decisions above - (B2 schema/min-count, B4 operating policy + allowed deltas + messaging) into a - local spec (not committed). This is requirements-only; it must not touch product - code or thresholds. -2. **`/skill:ralplan`** — plan the collection itself: per-language/per-source - waves, intake order, and the verification gate. Stop at pending approval. -3. **`/skill:ultragoal`** — durable ledger across sessions. One goal per - collection cell (e.g. "ZH AI-like × product-doc → n≥100"); checkpoint each as - counts reach target so progress survives session boundaries. -4. **`/skill:team`** — optional: parallelize collection across languages/sources - with workers + worktrees when the cells are independent. - -Each session uses the existing pipeline, e.g.: +The existing pipeline provides the following operations. Dataset size, +calibration targets, and collection strategy depend on the experiment. ```bash # Hash-only public-web human controls from a tracked source inventory @@ -143,25 +118,13 @@ npm run benchmark:rebaseline:fp-fixtures -- --dry-run npm run benchmark:ranges # refresh expected-ranges.json, review the diff ``` -## Exit / unblock gates - -- **B2 becomes valuable** when ≥1 metadata dimension beyond language/class/length - has ≥2 buckets each with n ≥ 5 reviewed rows (slices stop reading - `insufficient_data`/`unspecified`). No threshold change. -- **B3 stays report-only**; rerun `npm run benchmark:robustness` after fixtures - grow so retention rates are representative. No threshold change. -- **B4 becomes ready** only when ALL hold: (a) the four maintainer decisions are - recorded; (b) per-calibrated-language negatives ≥ the agreed minimum (≥100 for - a 1% FPR target, else strict zero-FP only); (c) AI-like + edited-AI positive - controls exist per language; (d) the benchmark stops being perfectly separable - (i.e., there is a real FP/FN trade-off to calibrate). Until then, B4 stays - blocked and thresholds are unchanged. - -## Guardrails (carry over) - -- Do not frame collection as detector bypass or evasion. -- Do not commit private or no-redistribution raw text — hashes/metadata/aggregates only. -- Do not tune a global threshold to fix one language/model-family miss cluster. -- Do not blend learner-Korean (or other stress envelopes) into native-human baselines. -- Do not publish broad ZH/JA claims from pilot data until the n ≥ 100 claim gate is met. -- Do not change detector thresholds (B4) outside an approved-delta, reviewed PR. +## Measurement considerations + +B2's slice report becomes more informative as metadata buckets gain enough +rows. B3's robustness report describes the fixtures it processes. B4's proposed +1% false-positive target needs a larger set of negatives than the original +49-fixture benchmark, and a useful calibration set needs examples of the +trade-off between false positives and missed detections. + +These observations describe the data gaps at the time of the plan. New work +can revisit the targets, corpus design, and implementation approach. diff --git a/docs/research/humanization-data-backlog.md b/docs/research/humanization-data-backlog.md index b7fcbe30..08a0519c 100644 --- a/docs/research/humanization-data-backlog.md +++ b/docs/research/humanization-data-backlog.md @@ -9,8 +9,7 @@ Scope: AI 문체 신호 완화와 자연스러운 글 품질 개선에 필요한 2026-09-08 결정: 보류했던 사람 블라인드 평가 #159와 사람 라벨 기반 짧은 글 코퍼스 #643은 폐기하고 `not_planned`로 종료했다. 아래에 남긴 -해당 설계와 요구사항은 이력이며 대기 작업이 아니다. 명시적인 새 요청 없이 -재개하지 않는다. 이 결정은 다른 연구의 실행이나 검증 기준 생략을 승인하지 않는다. +해당 설계와 요구사항은 당시 결정의 이력이다. ## Source Documents @@ -166,18 +165,9 @@ source_doc: Do not commit private raw text when a hash, manifest row, or aggregate score is enough. If raw examples are required for a public fixture, keep them small, license-compatible, and explicitly reviewed. -## Guardrails +## Priorities recorded on 2026-09-01 -- Do not frame this work as detector bypass or evasion. -- Do not tune global thresholds to fix one language/model family miss cluster. -- Do not merge pilot ZH/JA data into published broad claims until the claim gate is met. -- Do not use MPS proxy as a substitute for naturalness evaluation. -- Do not add lexicon entries without cold-corpus checks. -- Do not publish private generated or user-provided source text. - -## Suggested Order - -Execution order frozen for the next performance-only cycle (2026-09-01): +The following sequence records the plan at that date: 1. **KO GPT-family miss-review manifest.** Classify the currently available misses (up to 100) by register, deterministic signal breakdown, and root cause. Do not change thresholds or production behavior in this step. **Reviewed 2026-09-02 — GO (measure-only); design, data contract, taxonomy, procedure and acceptance criteria in [`ko-gpt-miss-review-step1-decision-20260902.md`](./ko-gpt-miss-review-step1-decision-20260902.md); implementation deferred to its own branches. Implemented 2026-09-02 (PR #718): hash-only discovery manifest (48 reviewed rows + 8 precondition exclusions from analyzer drift), blinded two-reviewer labels, and the measure-only report [`docs/benchmarks/ko-gpt-miss-review-v1.md`](../benchmarks/ko-gpt-miss-review-v1.md); taxonomy constants in [`ko-gpt-miss-taxonomy-v1.md`](./ko-gpt-miss-taxonomy-v1.md).** 2. **Edited-AI intake and corpus.** Freeze light/heavy edit policies and the manifest schema before generating samples. @@ -188,15 +178,15 @@ Execution order frozen for the next performance-only cycle (2026-09-01): 7. **KO register and lexicon calibration.** Use the miss review and real false-positive intake to remine evidence-backed entries and stress high-risk registers without global threshold inflation. 8. **ZH/JA corpus expansion.** Reach the same per-language class/register evidence gate before making broader claims. -Step 1 is complete (PR #718). Later steps still require their own acceptance -evidence; this historical sequence does not activate them automatically. +Step 1 completed in PR #718. The remaining entries describe proposed work +at the time of this record. -**Owner decision 2026-09-02:** with step 1 complete (PR #718), the survey's H-4b (specificity-preservation constraint on the plain rewrite) runs ahead of steps 2–8 as rewrite-efficacy **Study 4**, registered in [`2026-rewrite-efficacy-prereg.md`](./2026-rewrite-efficacy-prereg.md) before any data. Steps 2–8 stay in their frozen order behind it. +**Owner decision 2026-09-02:** with step 1 complete (PR #718), the survey's H-4b (specificity-preservation constraint on the plain rewrite) runs ahead of steps 2–8 as rewrite-efficacy **Study 4**, registered in [`2026-rewrite-efficacy-prereg.md`](./2026-rewrite-efficacy-prereg.md) before any data. Steps 2–8 were listed after it in that plan. **Status 2026-09-07:** [Study 4](./2026-rewrite-efficacy-study4.md) is complete without promotion in either language. The [September 6 scope decision](https://github.com/devswha/patina/issues/643#issuecomment-5559803306) defers human ratings, manual labels, contributor confirmations and manual -rights/counterfactual review. Those requirements remain unmet. Active short-form -work is limited to automated diagnostics on admissible, hash-bound inputs; -model scores are not human ratings or validated corpus error-rate gates. +rights/counterfactual review. At that date, short-form work used automated +diagnostics on hash-bound inputs, without human ratings or validated corpus +error-rate estimates. diff --git a/docs/research/ko-gpt-miss-review-step1-decision-20260902.md b/docs/research/ko-gpt-miss-review-step1-decision-20260902.md index 04e8ee8f..718cb0f8 100644 --- a/docs/research/ko-gpt-miss-review-step1-decision-20260902.md +++ b/docs/research/ko-gpt-miss-review-step1-decision-20260902.md @@ -1,5 +1,8 @@ # KO GPT-family miss-review manifest — step-1 review decision (2026-09-02) +> Historical study record (2026-09-02). The procedure and permitted diff below +> describe that completed investigation, not constraints on subsequent work. + Status: **GO (measure-only)** — external review complete; no collector, manifest, taxonomy, threshold, pattern, lexicon, or runtime change was made in this step. Roadmap: step 1 of the frozen performance-only order in `humanization-data-backlog.md`. Steps 2–8 stay inactive. diff --git a/docs/research/ko-translationese-scholarship.md b/docs/research/ko-translationese-scholarship.md index 3bf638fe..0e59f57f 100644 --- a/docs/research/ko-translationese-scholarship.md +++ b/docs/research/ko-translationese-scholarship.md @@ -17,9 +17,9 @@ intuition. supplies the *terminology and conceptual frame*; the calibration evidence is patina's own. - The signals remain **advisory only**: `translationese` and `koPostEditese.v1` - must not feed score, the document `hot` verdict, gates, severity, baselines, - percentiles, benchmark claims, or authorship verdicts. Any future coupling - requires the separately approved Phase 4 calibration package in + do not feed score, the document `hot` verdict, gates, severity, baselines, + percentiles, benchmark claims, or authorship verdicts in the current implementation. + Calibration ideas are described in [`TRANSLATIONESE-KO.md`](../TRANSLATIONESE-KO.md). ## Conceptual lineage diff --git a/process/pattern-freshness.md b/process/pattern-freshness.md index 81d167b3..0103e919 100644 --- a/process/pattern-freshness.md +++ b/process/pattern-freshness.md @@ -1,89 +1,44 @@ -# Pattern Freshness Process +# Pattern freshness -Status: quarterly review process. This is not a 2025+ benchmark report and must not be cited as evidence for catch-rate claims. -Related issues: #155, #160, #165. +Patterns and lexicons describe writing habits that can change as models and +usage change. This page maps the available evaluation tools and metadata. +Historical tracking includes issues #155, #160, and #165. -Patina's patterns and AI lexicons are maintained as falsifiable detection hypotheses, not permanent truths. Every refresh has to preserve two constraints: +## Evaluating a candidate -1. do not publish model-era performance claims without corpus evidence and confidence intervals; -2. do not add or keep a pattern/lexicon entry without a false-positive check against matched human controls. +Useful comparisons include examples where a candidate fires, matched human +controls, register-specific false positives, and before/after rewrites. +Precision, recall, document-frequency lift, and meaning changes answer different +questions. Sample size and uncertainty help describe how far a result generalizes. -## Cadence +A refresh can revise, replace, or remove a pattern or lexicon entry. Collection +size and experiment design depend on the question being investigated. -Run a freshness review once per quarter, in the first maintenance window after quarter close: - -| Quarter | Corpus freeze | Review target | Publish target | -|---|---|---|---| -| Q1 | Mar 31 | Apr week 2 | Apr week 4 | -| Q2 | Jun 30 | Jul week 2 | Jul week 4 | -| Q3 | Sep 30 | Oct week 2 | Oct week 4 | -| Q4 | Dec 31 | Jan week 2 | Jan week 4 | - -A release can skip a quarter only when the maintainer records an issue comment with the reason, the next scheduled window, and whether README performance claims remain safe to display. - -## Corpus refresh source - -Each refresh builds a redistributable or privately auditable corpus snapshot. Public text goes under `artifacts/` or `tests/fixtures/` only when redistribution is allowed; otherwise commit metadata, hashes, metrics, and reviewer notes. - -Minimum snapshot metadata: - -- `snapshot_id`: stable id such as `2026-q2-ko-en-register-refresh` -- `collected_at`: ISO date or date range -- `languages`: one or more of `ko`, `en`, `zh`, `ja` -- `registers`: blog, academic-summary, product-doc, chat/update, technical how-to, or a documented addition -- `classes`: AI-like, natural/human, lightly edited AI, heavily edited AI -- `generators`: at least GPT-family, Claude-family, Gemini-family for benchmark claims; open-weight models are encouraged but do not replace the three-family minimum -- `human_controls`: source, license, and matching rule by language/register -- `redistribution`: `public`, `private`, or `summary-only` -- `commands`: exact Patina commands and commit SHA used for scoring - -## Promotion criteria - -An emerging pattern or lexicon entry can move from candidate to shipped only when its tracking issue includes a 50-document evaluation fixture or manifest: - -- 25 hot documents where the candidate is expected to fire; -- 25 cold matched human/control documents where it should not fire; -- at least two registers unless the candidate is explicitly register-specific; -- no private text checked in without redistribution rights; -- `npm run benchmark` or a documented manual scoring command with the candidate enabled. - -Promotion thresholds: - -| Change type | Precision floor | Recall floor | False-positive gate | Extra rule | -|---|---:|---:|---:|---| -| new rewrite pattern | ≥0.80 on the 50-doc fixture | ≥0.50 | no severe false positives | before/after examples and semantic-risk note required | -| new score-only pattern | ≥0.70 | ≥0.40 | no severe false positives | must not alter rewrite output | -| lexicon entry | ≥4× hot-vs-cold document-frequency lift | n/a | cold document-frequency ≤5% unless register-scoped | needs per-entry provenance | -| threshold change | improves F1 or fixes documented FP/FN | n/a | must not regress protected fixtures | update expected ranges and benchmark report | - -If a candidate misses the floor, keep the issue open as `research`/`benchmark` or close it as not actionable; do not ship by lowering the gate after the fact. - -## Tracking issue requirements - -Use `.github/ISSUE_TEMPLATE/pattern_proposal.yml` for each emerging candidate. The issue must include: - -1. language and register scope; -2. the candidate signal and why existing patterns/lexicon do not already cover it; -3. a 50-doc fixture path, manifest path, or collection plan; -4. precision/recall or hot/cold firing evidence before merge; -5. false-positive risk and exclusion rule; -6. before/after rewrite examples when the candidate can rewrite prose. +```bash +npm run benchmark +npm run benchmark:report +npm run benchmark:signal-impact +npm run lexicon:freshness +``` -One issue can group closely related phrase variants only when they share the same trigger, false-positive risk, and fixture. +The optional [pattern proposal form](../.github/ISSUE_TEMPLATE/pattern_proposal.yml) +can collect examples and measurements. Existing corpus reports are in +[`docs/benchmarks/`](../docs/benchmarks/). -## Frontmatter metadata +## Corpus metadata -All shipped pattern packs and AI lexicons carry `corpus-snapshot:` frontmatter. +Corpus manifests commonly record a snapshot ID, collection date, languages, +registers, classes, generator models, human-control sources, redistribution +status, and scoring command/revision. A public manifest can contain hashes and +aggregates when source text is private. -Current allowed statuses: +## Frontmatter and provenance -- `current`: validated in the latest quarterly snapshot. -- `partial`: some evidence exists, but not enough for broad benchmark claims. -- `needs-quarterly-refresh`: legacy pattern pack awaiting its next quarterly snapshot. -- `needs-re-mine`: legacy lexicon awaiting per-entry corpus provenance and a 2025+ paired corpus remine. -- `needs-external-calibration`: seeded entry set that requires an external or larger-language corpus before public accuracy claims. +Pattern packs and lexicons use `corpus-snapshot:` frontmatter. Existing values +include `current`, `partial`, `needs-quarterly-refresh`, `needs-re-mine`, and +`needs-external-calibration`. These describe the recorded evidence state. -For newly mined or re-mined lexicon entries, add per-entry provenance before changing shipped behavior: +Per-entry lexicon provenance includes: ```yaml added: YYYY-MM-DD @@ -92,21 +47,10 @@ last_validated: YYYY-MM-DD lift: "hot/cold document-frequency ratio, e.g. 5.2x" ``` -Legacy lexicons can stay grouped under `corpus-snapshot:` until a remine lands, but they must not be described as fresh or 2025+ validated. - -Current lexicon provenance state: - -- English was re-mined on 2026-05-22 against the HAP-E English paired corpus and now stores per-entry lift evidence in `lexicon/provenance/ai-en.json`. -- Korean, Mandarin Chinese, and Japanese carry per-entry sidecars too, but legacy or starter entries keep `null` fields where the repository does not have entry-level source/lift evidence yet. - -Run the provenance guard before changing lexicon behavior: - -```bash -npm run lexicon:freshness -``` - -## Re-baseline claim gate - -Issue #155 remains blocked until an executed report measures catch rate and false-positive rate on at least three model families across at least two languages with n≥100 per cell and binomial 95% confidence intervals. Until that report lands, README claims should point to the checked-in deterministic benchmark and label it as such. +English entries include the 2026-05-22 HAP-E paired-corpus evidence in +`lexicon/provenance/ai-en.json`. Korean, Chinese, and Japanese also have sidecars; +legacy or starter entries use `null` where entry-level source/lift evidence is +unavailable. `npm run lexicon:freshness` checks sidecars against shipped entries. -Issue #160's English remine is complete, but issue #155 remains the broader public-claim gate. Do not cite lexicon remine results as overall detector catch-rate claims. +An entry's validation history and an overall detector-performance study measure +different things. Dated reports retain their corpus, model, and revision context. diff --git a/tests/unit/issue-forms.test.js b/tests/unit/issue-forms.test.js index dfa8ccd8..a3ac6edd 100644 --- a/tests/unit/issue-forms.test.js +++ b/tests/unit/issue-forms.test.js @@ -6,7 +6,6 @@ import yaml from 'js-yaml'; import test from 'node:test'; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); -const POLICY_URL = 'https://github.com/devswha/patina/blob/main/docs/WORKFLOW.md'; const FORM_PATHS = [ '.github/ISSUE_TEMPLATE/bug_report.yml', '.github/ISSUE_TEMPLATE/feature_request.yml', @@ -17,20 +16,25 @@ const FORM_PATHS = [ '.github/ISSUE_TEMPLATE/research_proposal.yml' ]; -function loadForm(relativePath) { - const source = readFileSync(resolve(REPO_ROOT, relativePath), 'utf8'); - return yaml.load(source); -} - -test('all issue forms link to workflow policy and collect scope and acceptance', () => { +test('issue forms have usable fields and unique identifiers', () => { for (const relativePath of FORM_PATHS) { - const form = loadForm(relativePath); - assert.ok(Array.isArray(form.body), `${relativePath} should define a body`); - const acceptance = form.body.find((field) => field.id === 'acceptance'); - assert.ok(acceptance, `${relativePath} should define an acceptance field`); - assert.equal(acceptance.type, 'textarea'); - assert.equal(acceptance.attributes.label, 'Scope and acceptance'); - assert.equal(acceptance.validations.required, true); - assert.match(JSON.stringify(form), new RegExp(POLICY_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); + const form = yaml.load(readFileSync(resolve(REPO_ROOT, relativePath), 'utf8')); + assert.ok(form.name && form.description, `${relativePath} needs a name and description`); + assert.ok(Array.isArray(form.body) && form.body.length > 0, `${relativePath} needs fields`); + const ids = new Set(); + for (const field of form.body) { + assert.ok(['markdown', 'input', 'textarea', 'dropdown', 'checkboxes'].includes(field.type), field.type); + if (field.type === 'markdown') { + assert.ok(field.attributes.value.trim(), `${relativePath} has an empty notice`); + continue; + } + assert.ok(field.id && !ids.has(field.id), `${relativePath}: duplicate or missing id ${field.id}`); + ids.add(field.id); + assert.ok(field.attributes.label, `${relativePath}: ${field.id} needs a label`); + if (field.validations?.required !== undefined) assert.equal(typeof field.validations.required, 'boolean'); + if (field.type === 'dropdown' || field.type === 'checkboxes') { + assert.ok(field.attributes.options.length > 0, `${relativePath}: ${field.id} needs choices`); + } + } } }); From ac699098f0fdbf2af71404f8ce84662899bd3fbd Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:47:54 +0900 Subject: [PATCH 08/10] test: retire fixed pattern-maintenance policy assertions --- tests/unit/pattern-docs.test.js | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/tests/unit/pattern-docs.test.js b/tests/unit/pattern-docs.test.js index 4c234bd3..578b9afc 100644 --- a/tests/unit/pattern-docs.test.js +++ b/tests/unit/pattern-docs.test.js @@ -150,18 +150,13 @@ test('pattern and lexicon packs carry corpus snapshot metadata', () => { } }); -test('pattern freshness process defines cadence, candidate fixtures, and promotion gates', () => { +test('pattern freshness guide references available evaluation commands', () => { const processDoc = readFileSync(resolve(REPO_ROOT, 'process/pattern-freshness.md'), 'utf8'); - assert.match(processDoc, /quarterly review process/i); - assert.match(processDoc, /50-document evaluation fixture/i); - assert.match(processDoc, /Precision floor/); - assert.match(processDoc, /Recall floor/); - assert.match(processDoc, /corpus-snapshot:/); - - const template = readFileSync(resolve(REPO_ROOT, '.github/ISSUE_TEMPLATE/pattern_proposal.yml'), 'utf8'); - assert.match(template, /id: register_scope/); - assert.match(template, /id: evaluation_fixture/); - assert.match(template, /id: measurement/); + const pkg = JSON.parse(readFileSync(resolve(REPO_ROOT, 'package.json'), 'utf8')); + const commands = new Set([...processDoc.matchAll(/npm run ([\w:-]+)/g)].map(match => match[1])); + for (const command of commands) { + assert.equal(typeof pkg.scripts[command], 'string', `unknown documented command: npm run ${command}`); + } }); test('viral-hook packs remain score-only with expanded severity-documented coverage', () => { From f4ba6799f5fe9d5965a84fc649b650b43e527c3b Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:50:22 +0900 Subject: [PATCH 09/10] docs: remove remaining frozen-work and contribution constraints --- docs/benchmarks/rebaseline-audit-ko-latest.md | 3 +-- docs/community/pattern-of-the-week.md | 8 +++++--- docs/research/humanization-literature-2026-09.md | 7 +++---- .../ko-gpt-miss-review-step1-decision-20260902.md | 2 +- .../ko-performance-improvement-handoff-20260818.md | 2 +- docs/research/provider-gemini-kimi-deepseek-20260905.md | 9 ++++----- docs/research/provider-groq-together-minimax-20260905.md | 5 ++++- 7 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/benchmarks/rebaseline-audit-ko-latest.md b/docs/benchmarks/rebaseline-audit-ko-latest.md index 3cdac8b1..d4deaf0b 100644 --- a/docs/benchmarks/rebaseline-audit-ko-latest.md +++ b/docs/benchmarks/rebaseline-audit-ko-latest.md @@ -89,8 +89,7 @@ construction (revisions of known AI positives). **Verdict: genuine.** - Edited-AI audited: **genuine**. - **No mislabeled or too-easy samples found.** The corpus is genuinely hard (overall TPR at 5% FPR is 0.0% — high-scoring human controls block low-FPR - operation), which is the honest measure-only outcome motivating a future, - separately-approved calibration delta. + operation), which motivated the calibration investigation recorded below. ## Post-calibration update (lexicon density_threshold 2.0 → 3.0) diff --git a/docs/community/pattern-of-the-week.md b/docs/community/pattern-of-the-week.md index e6e61acb..c7f8b05a 100644 --- a/docs/community/pattern-of-the-week.md +++ b/docs/community/pattern-of-the-week.md @@ -48,8 +48,10 @@ claims, it does not fact-check them. ## How to add the next one -1. Open a [pattern proposal](https://github.com/devswha/patina/issues/new?template=pattern_proposal.yml) or a [false-positive](https://github.com/devswha/patina/issues/new?template=false_positive.yml) report. -2. Follow [CONTRIBUTING.md](../../CONTRIBUTING.md). Include a success rewrite and a case that must not fire. -3. Edit the pack in `patterns/{lang}-*.md`. Do not bump the package version in the same PR. +The packs live in `patterns/{lang}-*.md`; [CONTRIBUTING.md](../../CONTRIBUTING.md) +describes their format. A success rewrite and a false-positive example help +explain a proposed change. A [pattern proposal](https://github.com/devswha/patina/issues/new?template=pattern_proposal.yml) +or [false-positive report](https://github.com/devswha/patina/issues/new?template=false_positive.yml) +can provide a place to discuss it. Starter issues use the `good first issue` and `patterns` labels. diff --git a/docs/research/humanization-literature-2026-09.md b/docs/research/humanization-literature-2026-09.md index 5884dd1b..2246feb2 100644 --- a/docs/research/humanization-literature-2026-09.md +++ b/docs/research/humanization-literature-2026-09.md @@ -33,10 +33,9 @@ rewrite that this survey is built around: 4. The human-evaluation panel (`human-eval-panel.md`, #159) is designed but has never run, and the meaning proxy (MPS) checks anchors only. -The frozen performance-only order in `humanization-data-backlog.md` keeps step 1 -(KO GPT-family miss review) as the sole active item. This note does **not** -activate steps 2–8; it records what the literature says each step should look -like, phrased as pre-registrable hypotheses in §9. +The September 2 backlog prioritized the KO GPT-family miss review. This survey +records possible follow-up work as pre-registrable hypotheses in §9; the +historical backlog does not fix the order of future investigations. Ethics frame (`docs/ETHICS.md`): patina's success metric is *perceived* AI-likeness by blind readers plus meaning preservation, not detector bypass. diff --git a/docs/research/ko-gpt-miss-review-step1-decision-20260902.md b/docs/research/ko-gpt-miss-review-step1-decision-20260902.md index 718cb0f8..292cab43 100644 --- a/docs/research/ko-gpt-miss-review-step1-decision-20260902.md +++ b/docs/research/ko-gpt-miss-review-step1-decision-20260902.md @@ -4,7 +4,7 @@ > describe that completed investigation, not constraints on subsequent work. Status: **GO (measure-only)** — external review complete; no collector, manifest, taxonomy, threshold, pattern, lexicon, or runtime change was made in this step. -Roadmap: step 1 of the frozen performance-only order in `humanization-data-backlog.md`. Steps 2–8 stay inactive. +Roadmap at the time: step 1 of the September 2 plan in `humanization-data-backlog.md`; steps 2–8 were outside this investigation. This record separates two voices on purpose: what the external reviewer said, and what the executing maintainer session concludes from it. Nothing below changes production behaviour. diff --git a/docs/research/ko-performance-improvement-handoff-20260818.md b/docs/research/ko-performance-improvement-handoff-20260818.md index b6707304..94870013 100644 --- a/docs/research/ko-performance-improvement-handoff-20260818.md +++ b/docs/research/ko-performance-improvement-handoff-20260818.md @@ -1,6 +1,6 @@ # Korean performance improvement handoff — 2026-08-18 -> **Status note (2026-09-02, index pass):** superseded by `ko-confirmatory-verdict-20260901.md` (NOT PROMOTED) and by the frozen order in `humanization-data-backlog.md`; retained for its external-evidence section and as input to the 2026-09-02 review handoff. +> **Status note (2026-09-02, index pass):** superseded by `ko-confirmatory-verdict-20260901.md` (NOT PROMOTED) and the September 2 plan in `humanization-data-backlog.md`; retained for its external-evidence section and as input to that day's review handoff. **Status:** research implementation complete; promotion blocked by live safety evidence. Production remains baseline. diff --git a/docs/research/provider-gemini-kimi-deepseek-20260905.md b/docs/research/provider-gemini-kimi-deepseek-20260905.md index 0a09ef48..d7885f5f 100644 --- a/docs/research/provider-gemini-kimi-deepseek-20260905.md +++ b/docs/research/provider-gemini-kimi-deepseek-20260905.md @@ -33,11 +33,10 @@ endpoint. Keep that distinction until the parent verifies response identity. Google documents these families in Antigravity, but does not establish the mapping of a local OpenCodex alias. [GTHINK] [GACCESS] -Every Gemini experiment must use **only** -`http://127.0.0.1:10100/v1` and the registered -`google-antigravity/gemini-*` route. No Gemini key, direct API call, or Gemini -CLI fallback is allowed by this study. Nothing in this research probed that -endpoint. +The September 5 study specified `http://127.0.0.1:10100/v1` and the registered +`google-antigravity/gemini-*` route for its Gemini cohort. Direct API and CLI +routes were outside that cohort. This research did not probe the endpoint; +these settings describe the dated study, not future experiment requirements. Google's 3.8 migration guide tells clients to remove `temperature`, `top_p`, `top_k` and the old thinking-budget control. Use the documented thinking-level diff --git a/docs/research/provider-groq-together-minimax-20260905.md b/docs/research/provider-groq-together-minimax-20260905.md index 95cc79a6..213f88d8 100644 --- a/docs/research/provider-groq-together-minimax-20260905.md +++ b/docs/research/provider-groq-together-minimax-20260905.md @@ -94,7 +94,10 @@ The baseline should keep the existing prompt-only score format. Strict structure The study's HTTP transport rejects remote non-HTTPS endpoints and embedded URL credentials. Its Gemini guard requires the parent's admitted loopback OpenCodex route and explicit `google-antigravity/gemini-*` identity. A Gemini API key, direct API request, or direct CLI fallback is outside this study. [Local transport](../../scripts/research/model-evaluation-transport.mjs#L39) -**Execution admission checklist** +**September 5 study protocol** + +The procedure below records that study's proposed design and ownership. It is +historical context for interpreting the study, not a current execution policy. 1. Parent supplies provider access, funded balance, spend ceiling and a new private output directory. Account access remains missing until observed. Confirm model lifecycle, region, price mode and actual limits. 2. Freeze source SHA, config/pattern/prompt hashes, fixture identities, selected candidates, settings and judge seats. Copy selected rows into a new admitted manifest. Carry upstream-family metadata and the existing fixed judge seats from the [reviewed protocol](model-evaluation-20260904.md); do not edit an active manifest or reuse a frozen collector. From 77b68e69b022a05d34e0aea370f664d40b469ee9 Mon Sep 17 00:00:00 2001 From: devswha Date: Wed, 30 Sep 2026 03:59:19 +0900 Subject: [PATCH 10/10] docs: clarify deployment source and current checkout bindings --- docs/integrations/release.md | 10 +++++++--- docs/operations/pro-launch.md | 17 +++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/docs/integrations/release.md b/docs/integrations/release.md index 27dc4b44..a628d5fe 100644 --- a/docs/integrations/release.md +++ b/docs/integrations/release.md @@ -17,9 +17,13 @@ Web deployment normally follows a reviewed feature PR into `main` through the existing Vercel project. A version-preparation PR and npm release tag are separate; a feature merge may reach the website first. Check the project's production branch and verify the production version and rewrite flow after deployment. -Vercel can build the merge commit through its Git integration, or a deployment -can be created with `vercel --prod` from a `main` checkout. Deployment history -contains previous production builds for rollback. +Vercel's Git integration builds the merge commit. The +[Vercel CLI](https://vercel.com/docs/cli/deploy) instead deploys the current +project directory, which can include local edits. To deploy the merged `main` +revision with `vercel --prod`, fetch `origin/main`, check that +`git status --porcelain` is empty, and compare `git rev-parse HEAD` with +`git rev-parse origin/main` before deploying. Deployment history contains +previous production builds for rollback. Release tags start the npm publication job; push a tag only for an intended release. The GitHub Release remains coupled to successful npm publication. diff --git a/docs/operations/pro-launch.md b/docs/operations/pro-launch.md index 2384c6c7..cdc12f0f 100644 --- a/docs/operations/pro-launch.md +++ b/docs/operations/pro-launch.md @@ -14,7 +14,7 @@ the inputs; deploy the resulting `playground/launch-config.js` with that release | Input | Disabled/default behavior | Enabled requirement | |---|---|---| | `PATINA_PRO_CHECKOUT_ENABLED` | Missing or `false` generates disabled checkout. | Must be exactly `true`. | -| `PATINA_DEPLOYMENT_CHANNEL` | Not used while checkout is disabled. | Exactly `staging` or `production` and the binding channel. | +| `PATINA_DEPLOYMENT_CHANNEL` | Not used while checkout is disabled. | Matches the binding channel; the current table has only `production`. The parser also recognizes `staging`. | | `PATINA_PRO_CHECKOUT_URL` | Not used while checkout is disabled. | Exact bound provider HTTPS checkout URL (production: `https://buy.polar.sh/polar_cl_*`): no userinfo, port, query, fragment, alternate host/subdomain, encoded path, or trailing slash. | | `PATINA_PRO_GATE_EVIDENCE_ID` | Not used while checkout is disabled. | Staging: `PAY-STG-`; production: `PAY-B-`; exact bound evidence ID in either case. | @@ -87,15 +87,20 @@ The generator accepts `PAY-STG-[A-Za-z0-9][A-Za-z0-9_-]*` for staging and channel, origin, and path against `scripts/checkout-evidence-bindings.mjs`. The Polar production binding is documented by `pay-b-binding-polar-20260729.json` and `pay-live-runtime-polar-20260729.json`. -These identifiers and bindings are runtime inputs. +Bindings are source-controlled configuration. Environment variables supply +the evidence ID and checkout URL at build time; the generator matches them +against that table. The generated public configuration is read at runtime. ## Staging and live configuration -A staging deployment uses `PATINA_DEPLOYMENT_CHANNEL=staging`, a matching -`PAY-STG-...` binding, and its checkout URL. Production uses `production` and -its `PAY-B-...` binding. In either case, setting +The current source table contains only the production Polar binding, so enabled +staging checkout is unsupported. Tests inject a staging binding into their +fixture; environment variables cannot add one to a deployment. + +Production uses `PATINA_DEPLOYMENT_CHANNEL=production`, the bound `PAY-B-...` +evidence ID and checkout URL, and Vercel's `VERCEL_ENV=production`. Setting `PATINA_PRO_CHECKOUT_ENABLED=true` and running `npm run launch-config:generate` -produces the enabled config when all inputs match. +produces the enabled config when those inputs match the source table. The public config contains `schemaVersion: 1`, channel, enabled state, checkout origin/path, and evidence ID. Useful browser checks include the CTA,