II.
Page JSON
Structured · livepage:docs-specs-platform-gaps-phase0-intake-rehearsal
WP-RN-02 record: public-push guard and intake rehearsal json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-specs-platform-gaps-phase0-intake-rehearsal",
"_kind": "Page",
"_file": "wiki/docs/specs/platform-gaps/phase0/intake-rehearsal.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/specs/platform-gaps/phase0/intake-rehearsal.md",
"sourceKind": "repo-docs",
"title": "WP-RN-02 record: public-push guard and intake rehearsal",
"displayName": "WP-RN-02 record: public-push guard and intake rehearsal",
"slug": "docs/specs/platform-gaps/phase0/intake-rehearsal",
"articlePath": "wiki/docs/specs/platform-gaps/phase0/intake-rehearsal.md",
"article": "\n# WP-RN-02 record: public-push guard and intake rehearsal\n\nExecuted 2026-09-28 on branch `phase0/hard-split-registry-guards` of `a5c-ai/fabric`. Spec: `docs/specs/platform-gaps/15-private-isolation-rename.md` WP-RN-02 (objective: codify the §1.4 intake process; make pushing fabric content to the public repository mechanically impossible from CI). Policy: `docs/DIVERGENCE-POLICY.md`; procedure: `docs/UPSTREAM-INTAKE.md`; cutover baseline: `watch-baseline.md`. The public repository `a5c-ai/babysitter` was read from and never written to (`pushed_at` stayed `2026-09-16T19:59:50Z` throughout; only `git fetch upstream --no-tags`, `git ls-remote`, `gh api` GET and `gh pr list -R a5c-ai/babysitter` were issued against it).\n\n## What landed\n\n| Item | Location |\n|---|---|\n| Guard script | `scripts/ci/public-push-guard.cjs` (CommonJS, Node >= 22, no dependencies; `--self-test`, `--root`, `--json`) |\n| Fixtures | `scripts/ci/fixtures/public-push-guard/negative/` (13, each declares `expect: <rule>`), `.../positive/` (9) |\n| Unit tests | `scripts/ci/public-push-guard.test.mjs` (`node:test`), `npm run test:public-push-guard`; scan alone: `npm run guard:public-push` |\n| CI job | `public-push-guard` in `.github/workflows/ci.yml` (permissions `contents: read`; `--self-test`, then asserts the checkout has no pushable remote for the public repository) |\n| Intake procedure | `docs/UPSTREAM-INTAKE.md` |\n| PR label | `upstream-intake` on `a5c-ai/fabric` (`0E8A16`, \"Cherry-picked from public a5c-ai/babysitter\") |\n| PR template | `.github/PULL_REQUEST_TEMPLATE/upstream-intake.md` (the default `PULL_REQUEST_TEMPLATE.md` is untouched) |\n| Policy link | `docs/DIVERGENCE-POLICY.md` §4 now points at the real intake doc, template, guard and this record |\n\n## Guard rules\n\n| Rule | Trips on | Allowed form |\n|---|---|---|\n| `push-to-public` | `git push <public url>`; `git push <remote>` when the file binds that remote to the public repository | `git push origin ...` |\n| `pushable-remote` | `git remote add` / `git config remote.<n>.url` / `git clone` / `gh repo clone` of the public repository without `git remote set-url --push <n> no_push` (or `remote.<n>.pushurl no_push`) in the same file | the fetch-only idiom of `.github/workflows/upstream-propagation-watch.yml` |\n| `push-url-set` | `git remote set-url --push <n> <public url>`, `git config remote.<n>.pushurl <public url>` | `... no_push` |\n| `ssh-url` | `git@github.com:a5c-ai/babysitter` outside a `git fetch` / `ls-remote` / remote-binding line | `git fetch git@github.com:a5c-ai/babysitter.git main` |\n| `gh-write` | `gh api` with `-X POST/PUT/PATCH/DELETE` or request fields against `repos/a5c-ai/babysitter...`; any non-read `gh <group> <verb> -R a5c-ai/babysitter` (`pr create`, `release create`, `workflow run`, ...) | `gh api repos/a5c-ai/babysitter --jq ...`, `gh pr list -R a5c-ai/babysitter` |\n| `checkout-with-write` | `actions/checkout` with `repository: a5c-ai/babysitter` plus `token:`, `ssh-key:`, `persist-credentials: true`, or `contents: write` / `write-all` in the file | `persist-credentials: false` under `contents: read` |\n| `action-targets-public` | any other step whose `with:` block (including JSON inside prompts) carries `repository:` / `repo:` = the public repository | `uses: a5c-ai/babysitter/<path>@<ref>` itself; `${{ github.repository }}` |\n| `submodule-public` | `.gitmodules` `url` pointing at the public repository | — |\n\nScanned surface: `.github/**`, `scripts/**` (minus the guard, its test and its fixtures), `package.json`, `.npmrc`, `.gitmodules`; file types `.yml .yaml .json .cjs .mjs .sh`. Comments and Markdown are never findings; `@a5c-ai/*` npm names never match. Escape hatch: `# public-push-guard: allow <reason>` (trailing or on the preceding comment line; the reason is mandatory, a bare marker still fails).\n\n## Evidence: guard\n\n```text\n$ node scripts/ci/public-push-guard.cjs --self-test\nPASS fixtures present -- 13 negative, 9 positive\nPASS negative scripts/ci/fixtures/public-push-guard/negative/action-targets-public.workflow.yml trips -- rules: action-targets-public\nPASS negative .../allow-marker-without-reason.workflow.yml trips -- rules: push-to-public\nPASS negative .../checkout-with-token.workflow.yml trips -- rules: checkout-with-write\nPASS negative .../checkout-write-permissions.workflow.yml trips -- rules: checkout-with-write\nPASS negative .../clone-without-no-push.sh trips -- rules: pushable-remote\nPASS negative .../gh-api-post.sh trips -- rules: gh-write\nPASS negative .../gh-pr-create.workflow.yml trips -- rules: gh-write\nPASS negative .../git-push-https.workflow.yml trips -- rules: push-to-public\nPASS negative .../package-scripts.json trips -- rules: push-to-public\nPASS negative .../push-named-remote.sh trips -- rules: push-to-public\nPASS negative .../remote-add-ssh-without-no-push.workflow.yml trips -- rules: pushable-remote\nPASS negative .../set-url-push.sh trips -- rules: push-url-set\nPASS negative .../submodule/.gitmodules trips -- rules: submodule-public\nPASS positive .../allow-marker.sh passes -- 2 suppressed by allow marker\nPASS positive .../checkout-read-only.workflow.yml passes\nPASS positive .../config-push-url-no-push.sh passes\nPASS positive .../documentation.md passes\nPASS positive .../fetch-only-upstream.workflow.yml passes\nPASS positive .../gh-read-only.sh passes\nPASS positive .../npm-names.workflow.yml passes\nPASS positive .../package-metadata.json passes\nPASS positive .../uses-action-reference.workflow.yml passes\nPASS repository scan (118 files) passes\n\npublic-push-guard self-test: 24/24 assertions passed\npublic-push-guard: self-test passed; repository scan clean (118 files, 0 findings)\n$ npm run test:public-push-guard\n# tests 14 # pass 14 # fail 0\n```\n\nThe first real scan (before any fix) reported three findings, all in inherited, disabled agent workflows, and they were fixed rather than allow-listed:\n\n```text\n.github/workflows/issue-triage-dispatch.yml:76: [action-targets-public] step \"uses: a5c-ai/babysitter/packages/adapters/triggers@staging\" is pointed at a5c-ai/babysitter\n > { \"issueNumber\": ${{ inputs.issue_number || github.event.issue.number }}, \"repo\": \"a5c-ai/babysitter\" }\n.github/workflows/live-stack-gardening.yml:51: [pushable-remote] git clone binds remote \"origin\" to a5c-ai/babysitter without `git remote set-url --push origin no_push`\n > git clone https://github.com/a5c-ai/babysitter.wiki.git /tmp/babysitter-wiki\n.github/workflows/live-stack-gardening.yml:73: [action-targets-public] step \"uses: a5c-ai/babysitter/packages/adapters/triggers@staging\" is pointed at a5c-ai/babysitter\n > Inputs: { \"repo\": \"a5c-ai/babysitter\", \"wikiPath\": \"/tmp/babysitter-wiki\" }\n```\n\nBoth workflows now act on `${{ github.repository }}` (an agent dispatched from fabric with a token must not triage the public repository's issues or edit its wiki). `js-yaml` loads `ci.yml`, `live-stack-gardening.yml` and `issue-triage-dispatch.yml`; the new job's steps are `Checkout repository`, `Setup Node.js`, `Guard self-test and repository scan`, `No pushable remote for the public repository`, permissions `{\"contents\":\"read\"}`.\n\n## Rehearsal: candidate review\n\n`git fetch upstream --no-tags` then `git log --oneline --remotes=upstream --not --remotes=origin` listed 469 public commits absent from every fabric ref; `origin/main..upstream/main` and `origin/staging..upstream/develop` (filtered) were empty; `origin/staging..upstream/staging` held three. Nearly all 469 are bot commits on `agent-versions/daily-*`, `model-versions/daily-*` and `changelog/daily-*` branches. Evaluation per `docs/UPSTREAM-INTAKE.md` §2:\n\n| Candidate | Branch | Decision | Reason |\n|---|---|---|---|\n| `d98b7ff70` docs: deployment is handled from a private deployment repository | `staging` | rejected | every hunk says deployment happens outside the repository; fabric is the deployment repository (and `git apply --check` already failed: fabric's `docs/development/06-kradle-cloud-platform.md` has moved on) |\n| `02e3a8773` ci: remove the cluster deploy pipeline from the public publish workflows | `staging` | rejected | deletes `publish.yml` deploy jobs and `g0-rt-jitsi-e2e.yml`, which fabric runs |\n| `64fd9f475` test(ci): pin publish workflows to have no cluster deploy jobs | `staging` | rejected | asserts the absence of jobs fabric has |\n| `d5c7e7f77` fix(sdk): avoid non-portable fallback sdk symlinks | `agent/issue-1757` | rejected | 3-way cherry-pick (tried in a throwaway worktree, then aborted) conflicted in `packages/babysitter-sdk/src/cli/main/runSupport.ts:291-303` and `packages/genty/platform/.../validation.ts:107-116`, exactly where public `main` later chose relative symlink targets (`ded587848`, 2026-08-14, already in fabric); superseded upstream, fabric wins leaves nothing |\n| `442e7ed6a` feat(sdk): expose runId, runDir, artifactsDir on ProcessContext | `feat/expose-runid-artifactsdir-on-process-context` | rejected | already in fabric (`packages/babysitter-sdk/src/runtime/types.ts` carries the identical fields and doc comments); the commit predates the public `packages/sdk` -> `packages/babysitter-sdk` move (`47fd0e280`, 2026-06-06) |\n| `58891e42a` docs: apply enforcement messaging | `docs/improve-readme-and-best-practices` | rejected | reverted on its own branch by `b62f317ac` |\n| `776123a8d` fix: create CONTRIBUTING.md | `copilot/fix-broken-contributing-link` | rejected | already reachable from fabric refs (0 commits beyond `origin`) |\n| `44e7f47ba` docs: daily changelog update (2026-08-05) | `changelog/daily-2026-08-05` | rejected | modifies `CHANGELOG.md` where fabric has diverged (fabric-only kip-sdk entries) and a `docs/daily-updates/2026-08-05.md` created earlier on the same bot branch |\n| `12eafcc00` / `96ad4691b` Cursor and Hermes records / evidence sources for 2026-07-15 | `agent-versions/daily-2026-07-15` | deferred | natural tight-series follow-up to the commit taken below; left out so the rehearsal is exactly one commit |\n| `76434c235` Add upstream agent versions for 2026-07-15 | `agent-versions/daily-2026-07-15` | **taken** | one commit, one new file, same shape as the 2026-07-14 and 2026-07-17 records fabric already has; fills a gap in fabric's own catalog data; nothing about it misdescribes fabric |\n\n## Rehearsal: the documented flow, as run\n\n```text\n$ git remote -v\norigin git@github.com:a5c-ai/fabric.git (fetch)\norigin git@github.com:a5c-ai/fabric.git (push)\nupstream https://github.com/a5c-ai/babysitter.git (fetch)\nupstream no_push (push)\n\n$ git fetch upstream --no-tags\n$ git switch -c intake/76434c2 # off phase0/hard-split-registry-guards at 030920ec6\n$ git cherry-pick -x 76434c235\n 1 file changed, 286 insertions(+)\n create mode 100644 packages/atlas/graph/agent-stack/agent-versions/upstream-current-2026-07-15.yaml\n$ git log -1 --format='%h %an %s%n%b'\nd39bba75d a5c-ai[bot] Add upstream agent versions for 2026-07-15\n(cherry picked from commit 76434c235de18ea1c0aceb6f5297a87cef9e861c)\n\n$ node -e '<js-yaml structural check>'\n07-15 docs: 8 kinds: AgentVersion\n07-14 docs: 10 kinds: AgentVersion\nsame top-level keys as 07-14: true\nfiles: 35 ids: 177 unparsable (pre-existing): pi-issue-541.yaml\nduplicate ids involving the new file: none\nnew ids: agentVersion:amp:0-0-1784091630-gf43d5c, agentVersion:claude:2-1-210, agentVersion:claude-agent-sdk:0-3-210,\n agentVersion:codex-sdk:6-47-0, agentVersion:droid:0-172-0, agentVersion:omp:16-5-2, agentVersion:opencode:1-18-1, agentVersion:pi:0-80-7\n\n$ npm run build:index --workspace=@a5c-ai/atlas # the consumer of these records\n[atlas:indexer] wrote dist/index.json\n[atlas:indexer] stats {\"totalRecords\":17715,\"totalEdges\":88686,\"totalNodeKinds\":245,\"totalEdgeKinds\":408,\"totalClusters\":20,\"yamlFiles\":2745,\"parseErrors\":1}\n(exit 0; the one parse error is pi-issue-541.yaml, which fails strict YAML at 030920ec6 too; all 8 new ids present in dist/index.json)\n\n$ node scripts/ci/public-push-guard.cjs --self-test # 24/24, repository scan clean\n$ git push -u origin intake/76434c2\n$ gh pr list -R a5c-ai/babysitter --head agent-versions/daily-2026-07-15 --state all # read-only provenance lookup\n1422 OPEN https://github.com/a5c-ai/babysitter/pull/1422 Add upstream agent versions for 2026-07-15\n$ gh pr create -R a5c-ai/fabric --base phase0/hard-split-registry-guards --head intake/76434c2 --label upstream-intake \\\n --title \"intake: Add upstream agent versions for 2026-07-15 (cherry-pick 76434c235)\" --body-file <template filled in>\nhttps://github.com/a5c-ai/fabric/pull/2\n$ gh pr view 2 --json state,labels,baseRefName,headRefName\n{\"base\":\"phase0/hard-split-registry-guards\",\"head\":\"intake/76434c2\",\"labels\":[\"upstream-intake\"],\"number\":2,\"state\":\"OPEN\"}\n$ git switch phase0/hard-split-registry-guards\n```\n\nPath map recorded in the PR: `packages/atlas/graph/agent-stack/agent-versions/upstream-current-2026-07-15.yaml` -> identical path (new file). Conflicts: none. Dropped hunks: none.\n\n## Status against the acceptance criteria\n\n- AC1 (intake doc: remote setup, labeling, path map, conflict policy): `docs/UPSTREAM-INTAKE.md` §1, §4, §5, §6. Done.\n- AC2 (CI guard fails push targets and checkout-with-write): guard + `public-push-guard` job; negative fixtures prove both classes trip. Done.\n- AC3 (rehearsal cherry-pick of one real upstream commit lands via the documented flow with its path map recorded): pull request **opened** — <https://github.com/a5c-ai/fabric/pull/2> — through the documented flow, labeled, path map recorded. **Not merged** by WP-RN-02; merging is the owner's decision (the branch ruleset requires a reviewed PR anyway). The spec's test requirement \"rehearsal PR merged and referenced\" therefore stays open until the owner merges #2; the reference is recorded here and in `docs/UPSTREAM-INTAKE.md` §9.\n- Test requirement (self-test fixture asserting the guard trips): `--self-test` and `scripts/ci/public-push-guard.test.mjs`. Done.\n- WP-RN-01 deferred requirement (\"CI check from WP-RN-02 green\"): the `public-push-guard` job runs on every pull request. First run, on the CI run of #2 (`pull_request`, run 36395354707): job `Public-push guard (two-repo split)` <https://github.com/a5c-ai/fabric/actions/runs/36395354707/job/108840321702> concluded `success`; steps `Checkout repository`, `Setup Node.js`, `Guard self-test and repository scan`, `No pushable remote for the public repository` all `success`. Green.\n\n## Rollback\n\n| Piece | Rollback |\n|---|---|\n| Guard, job, fixtures, tests, docs, template, workflow input fixes | `git revert 030920ec6` (and the follow-up docs commit) |\n| Label | `gh label delete upstream-intake -R a5c-ai/fabric` (do not: the propagation watch keys on it) |\n| Rehearsal PR | close #2 without merging; `git push origin --delete intake/76434c2` |\n\nNothing here touched the public repository, so there is nothing to roll back there.\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs",
"to": "page:docs-specs-platform-gaps-phase0-intake-rehearsal",
"kind": "contains_page"
}
]
}