II.
Page overview
Reference · livepage:docs-specs-platform-gaps-phase0-intake-rehearsal
WP-RN-02 record: public-push guard and intake rehearsal overview
Inspect the raw attributes, linked wiki pages, and inbound or outbound graph edges for page:docs-specs-platform-gaps-phase0-intake-rehearsal.
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
# WP-RN-02 record: public-push guard and intake rehearsal
Executed 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).
## What landed
| Item | Location |
|---|---|
| Guard script | `scripts/ci/public-push-guard.cjs` (CommonJS, Node >= 22, no dependencies; `--self-test`, `--root`, `--json`) |
| Fixtures | `scripts/ci/fixtures/public-push-guard/negative/` (13, each declares `expect: <rule>`), `.../positive/` (9) |
| Unit tests | `scripts/ci/public-push-guard.test.mjs` (`node:test`), `npm run test:public-push-guard`; scan alone: `npm run guard:public-push` |
| 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) |
| Intake procedure | `docs/UPSTREAM-INTAKE.md` |
| PR label | `upstream-intake` on `a5c-ai/fabric` (`0E8A16`, "Cherry-picked from public a5c-ai/babysitter") |
| PR template | `.github/PULL_REQUEST_TEMPLATE/upstream-intake.md` (the default `PULL_REQUEST_TEMPLATE.md` is untouched) |
| Policy link | `docs/DIVERGENCE-POLICY.md` §4 now points at the real intake doc, template, guard and this record |
## Guard rules
| Rule | Trips on | Allowed form |
|---|---|---|
| `push-to-public` | `git push <public url>`; `git push <remote>` when the file binds that remote to the public repository | `git push origin ...` |
| `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` |
| `push-url-set` | `git remote set-url --push <n> <public url>`, `git config remote.<n>.pushurl <public url>` | `... no_push` |
| `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` |
| `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` |
| `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` |
| `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 }}` |
| `submodule-public` | `.gitmodules` `url` pointing at the public repository | — |
Scanned 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).
## Evidence: guard
```text
$ node scripts/ci/public-push-guard.cjs --self-test
PASS fixtures present -- 13 negative, 9 positive
PASS negative scripts/ci/fixtures/public-push-guard/negative/action-targets-public.workflow.yml trips -- rules: action-targets-public
PASS negative .../allow-marker-without-reason.workflow.yml trips -- rules: push-to-public
PASS negative .../checkout-with-token.workflow.yml trips -- rules: checkout-with-write
PASS negative .../checkout-write-permissions.workflow.yml trips -- rules: checkout-with-write
PASS negative .../clone-without-no-push.sh trips -- rules: pushable-remote
PASS negative .../gh-api-post.sh trips -- rules: gh-write
PASS negative .../gh-pr-create.workflow.yml trips -- rules: gh-write
PASS negative .../git-push-https.workflow.yml trips -- rules: push-to-public
PASS negative .../package-scripts.json trips -- rules: push-to-public
PASS negative .../push-named-remote.sh trips -- rules: push-to-public
PASS negative .../remote-add-ssh-without-no-push.workflow.yml trips -- rules: pushable-remote
PASS negative .../set-url-push.sh trips -- rules: push-url-set
PASS negative .../submodule/.gitmodules trips -- rules: submodule-public
PASS positive .../allow-marker.sh passes -- 2 suppressed by allow marker
PASS positive .../checkout-read-only.workflow.yml passes
PASS positive .../config-push-url-no-push.sh passes
PASS positive .../documentation.md passes
PASS positive .../fetch-only-upstream.workflow.yml passes
PASS positive .../gh-read-only.sh passes
PASS positive .../npm-names.workflow.yml passes
PASS positive .../package-metadata.json passes
PASS positive .../uses-action-reference.workflow.yml passes
PASS repository scan (118 files) passes
public-push-guard self-test: 24/24 assertions passed
public-push-guard: self-test passed; repository scan clean (118 files, 0 findings)
$ npm run test:public-push-guard
# tests 14 # pass 14 # fail 0
```
The first real scan (before any fix) reported three findings, all in inherited, disabled agent workflows, and they were fixed rather than allow-listed:
```text
.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
> { "issueNumber": ${{ inputs.issue_number || github.event.issue.number }}, "repo": "a5c-ai/babysitter" }
.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`
> git clone https://github.com/a5c-ai/babysitter.wiki.git /tmp/babysitter-wiki
.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
> Inputs: { "repo": "a5c-ai/babysitter", "wikiPath": "/tmp/babysitter-wiki" }
```
Both 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"}`.
## Rehearsal: candidate review
`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:
| Candidate | Branch | Decision | Reason |
|---|---|---|---|
| `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) |
| `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 |
| `64fd9f475` test(ci): pin publish workflows to have no cluster deploy jobs | `staging` | rejected | asserts the absence of jobs fabric has |
| `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 |
| `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) |
| `58891e42a` docs: apply enforcement messaging | `docs/improve-readme-and-best-practices` | rejected | reverted on its own branch by `b62f317ac` |
| `776123a8d` fix: create CONTRIBUTING.md | `copilot/fix-broken-contributing-link` | rejected | already reachable from fabric refs (0 commits beyond `origin`) |
| `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 |
| `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 |
| `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 |
## Rehearsal: the documented flow, as run
```text
$ git remote -v
origin git@github.com:a5c-ai/fabric.git (fetch)
origin git@github.com:a5c-ai/fabric.git (push)
upstream https://github.com/a5c-ai/babysitter.git (fetch)
upstream no_push (push)
$ git fetch upstream --no-tags
$ git switch -c intake/76434c2 # off phase0/hard-split-registry-guards at 030920ec6
$ git cherry-pick -x 76434c235
1 file changed, 286 insertions(+)
create mode 100644 packages/atlas/graph/agent-stack/agent-versions/upstream-current-2026-07-15.yaml
$ git log -1 --format='%h %an %s%n%b'
d39bba75d a5c-ai[bot] Add upstream agent versions for 2026-07-15
(cherry picked from commit 76434c235de18ea1c0aceb6f5297a87cef9e861c)
$ node -e '<js-yaml structural check>'
07-15 docs: 8 kinds: AgentVersion
07-14 docs: 10 kinds: AgentVersion
same top-level keys as 07-14: true
files: 35 ids: 177 unparsable (pre-existing): pi-issue-541.yaml
duplicate ids involving the new file: none
new ids: agentVersion:amp:0-0-1784091630-gf43d5c, agentVersion:claude:2-1-210, agentVersion:claude-agent-sdk:0-3-210,
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
$ npm run build:index --workspace=@a5c-ai/atlas # the consumer of these records
[atlas:indexer] wrote dist/index.json
[atlas:indexer] stats {"totalRecords":17715,"totalEdges":88686,"totalNodeKinds":245,"totalEdgeKinds":408,"totalClusters":20,"yamlFiles":2745,"parseErrors":1}
(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)
$ node scripts/ci/public-push-guard.cjs --self-test # 24/24, repository scan clean
$ git push -u origin intake/76434c2
$ gh pr list -R a5c-ai/babysitter --head agent-versions/daily-2026-07-15 --state all # read-only provenance lookup
1422 OPEN https://github.com/a5c-ai/babysitter/pull/1422 Add upstream agent versions for 2026-07-15
$ gh pr create -R a5c-ai/fabric --base phase0/hard-split-registry-guards --head intake/76434c2 --label upstream-intake \
--title "intake: Add upstream agent versions for 2026-07-15 (cherry-pick 76434c235)" --body-file <template filled in>
https://github.com/a5c-ai/fabric/pull/2
$ gh pr view 2 --json state,labels,baseRefName,headRefName
{"base":"phase0/hard-split-registry-guards","head":"intake/76434c2","labels":["upstream-intake"],"number":2,"state":"OPEN"}
$ git switch phase0/hard-split-registry-guards
```
Path 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.
## Status against the acceptance criteria
- AC1 (intake doc: remote setup, labeling, path map, conflict policy): `docs/UPSTREAM-INTAKE.md` §1, §4, §5, §6. Done.
- AC2 (CI guard fails push targets and checkout-with-write): guard + `public-push-guard` job; negative fixtures prove both classes trip. Done.
- 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.
- Test requirement (self-test fixture asserting the guard trips): `--self-test` and `scripts/ci/public-push-guard.test.mjs`. Done.
- 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.
## Rollback
| Piece | Rollback |
|---|---|
| Guard, job, fixtures, tests, docs, template, workflow input fixes | `git revert 030920ec6` (and the follow-up docs commit) |
| Label | `gh label delete upstream-intake -R a5c-ai/fabric` (do not: the propagation watch keys on it) |
| Rehearsal PR | close #2 without merging; `git push origin --delete intake/76434c2` |
Nothing here touched the public repository, so there is nothing to roll back there.
documents
[]
Outgoing edges
None.
Incoming edges
contains_page1
- page:docs·PageBabysitter Docs