II.
Page JSON
Structured · livepage:docs-specs-platform-gaps-phase0-ar-provisioning
WP-RN-03 record: `@fabricstack` scope + Artifact Registry npm provisioning json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-specs-platform-gaps-phase0-ar-provisioning",
"_kind": "Page",
"_file": "wiki/docs/specs/platform-gaps/phase0/ar-provisioning.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/specs/platform-gaps/phase0/ar-provisioning.md",
"sourceKind": "repo-docs",
"title": "WP-RN-03 record: `@fabricstack` scope + Artifact Registry npm provisioning",
"displayName": "WP-RN-03 record: `@fabricstack` scope + Artifact Registry npm provisioning",
"slug": "docs/specs/platform-gaps/phase0/ar-provisioning",
"articlePath": "wiki/docs/specs/platform-gaps/phase0/ar-provisioning.md",
"article": "\n# WP-RN-03 record: `@fabricstack` scope + Artifact Registry npm provisioning\n\n> **Scope re-locked to `@fabricstack` on 2026-09-28, see `scope-relock.md`.** This record was produced under the previous private scope `@agentfabric`; its scope strings were rewritten mechanically at the re-lock (the quoted run logs and transcripts below therefore originally showed the old name, and the smoke package they refer to is the old-scope `0.0.0-smoke.2`, now a cleanup candidate in AR). The re-run under the new scope is recorded in `scope-relock.md`. The Artifact Registry repository, IAM policy and WIF principal are scope-agnostic and unchanged.\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-03 (§2 scope decision, §4.1 registry provisioning). Owner approval for the GCP changes was given at the `phase0.gcp-provision` breakpoint (\"Approve (gcloud re-authed) — create fabric-npm + the two IAM bindings, run the WIF smoke publish and the local install\"). Developer/CI instructions: `docs/user-guide/private-install.md`. The public repository `a5c-ai/babysitter` was never written to (`pushed_at` stayed `2026-09-16T19:59:50Z` throughout; only `gh api` GET calls were issued against it) and nothing was published to `registry.npmjs.org`.\n\n## What was created\n\n| Item | Value |\n|---|---|\n| Scope | `@fabricstack` (spec 15 §2; final lock at Gate B) |\n| GCP project / region | `boot-464019` / `us-central1` |\n| Artifact Registry repository | `projects/boot-464019/locations/us-central1/repositories/fabric-npm`, format `NPM`, mode `STANDARD_REPOSITORY`, description \"agentfabric private npm packages (fabric)\", created `2026-09-28T15:06:04Z`, Google-managed encryption, vulnerability scanning disabled (`containerscanning.googleapis.com` not enabled) |\n| Registry URL | `https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/` |\n| Committed root `.npmrc` | `@fabricstack:registry=https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/` plus a comment line; the pre-existing workspace lines (`link-workspace-packages`, `prefer-workspace-packages`, from commit `1f3ea8ad8`) were kept; zero credentials |\n| Smoke fixture | `scripts/ci/fixtures/ar-smoke/package/` (`@fabricstack/smoke`, `publishConfig.registry` = registry URL, `index.js` exports `{ ok: true }`) |\n| Smoke workflow | `.github/workflows/ar-smoke.yml` (id `369233665`), `workflow_dispatch`; a `push` trigger scoped to its own file only registers it on feature branches, the job is gated on `github.event_name == 'workflow_dispatch'` |\n| Developer/CI doc | `docs/user-guide/private-install.md` |\n\nCreation detail: `gcloud artifacts repositories create fabric-npm --repository-format=npm ...` failed twice with `INVALID_ARGUMENT: Maven config is not supported for format \"NPM\"` (a gcloud client defect, also with `--mode=standard-repository`), so the repository was created with the same parameters through the REST API (`POST https://artifactregistry.googleapis.com/v1/projects/boot-464019/locations/us-central1/repositories?repositoryId=fabric-npm`, body `{\"format\":\"NPM\",\"mode\":\"STANDARD_REPOSITORY\",\"description\":\"agentfabric private npm packages (fabric)\"}`, operation `d629ee5e-5f29-49bb-8122-db614d5b2ac8`). `gcloud artifacts repositories describe fabric-npm --location=us-central1 --project=boot-464019` confirms the values in the table.\n\nThe spec's second `.npmrc` line (`//us-central1-npm.pkg.dev/boot-464019/fabric-npm/:always-auth=true`) was not added: `always-auth` is not an npm >= 9 option (npm 10 ignores it with a warning), and the brief for this work package asked for the scope-routing line only. Artifact Registry npm requires a bearer token on every request regardless, which the helper provides.\n\n## IAM (repository-scoped only; nothing granted at project level)\n\nThe CI identity is the existing WIF pair from the `a5c-ai` organization variables (visibility `ALL`, so no `environment:` is needed in workflows): `GCP_SERVICE_ACCOUNT=github-deployer@boot-464019.iam.gserviceaccount.com`, `GCP_WORKLOAD_IDENTITY_PROVIDER=projects/1024539647872/locations/global/workloadIdentityPools/github/providers/a5c-ai`, `GCP_PROJECT_ID=boot-464019`. No new service account or WIF pool was created.\n\nCommands run:\n\n```bash\ngcloud artifacts repositories add-iam-policy-binding fabric-npm --location=us-central1 --project=boot-464019 \\\n --member=serviceAccount:github-deployer@boot-464019.iam.gserviceaccount.com --role=roles/artifactregistry.writer\ngcloud artifacts repositories add-iam-policy-binding fabric-npm --location=us-central1 --project=boot-464019 \\\n --member=user:tal@a5c.ai --role=roles/artifactregistry.reader\ngcloud artifacts repositories get-iam-policy fabric-npm --location=us-central1 --project=boot-464019 --format=json\n```\n\nFinal policy, verbatim:\n\n```json\n{\n \"bindings\": [\n {\n \"members\": [\n \"user:tal@a5c.ai\"\n ],\n \"role\": \"roles/artifactregistry.reader\"\n },\n {\n \"members\": [\n \"serviceAccount:github-deployer@boot-464019.iam.gserviceaccount.com\"\n ],\n \"role\": \"roles/artifactregistry.writer\"\n }\n ],\n \"etag\": \"BwZcjGj6Uio=\",\n \"version\": 1\n}\n```\n\nHuman principal set: `user:tal@a5c.ai` (reader). Additional developers get `roles/artifactregistry.reader` on the repository with the first command above; writer stays reserved for the CI service account (spec 15 §4.1: \"humans reader-by-default, writer only for release owners\").\n\n## WIF principal (pre-existing, unchanged)\n\n`gcloud iam service-accounts get-iam-policy github-deployer@boot-464019.iam.gserviceaccount.com` shows `roles/iam.workloadIdentityUser` for `principalSet://iam.googleapis.com/projects/1024539647872/locations/global/workloadIdentityPools/github/attribute.repository/a5c-ai/fabric` (alongside `a5c-ai/babysitter`, `a5c-ai/company`, `a5c-ai/osb`). The pool provider `github/providers/a5c-ai` has attribute condition `assertion.repository_owner == \"a5c-ai\"` and maps `attribute.repository`. No WIF change was needed or made.\n\n## Smoke run (AC1, AC2 publish half)\n\n- Registration: the first `gh workflow run ar-smoke.yml --ref phase0/hard-split-registry-guards` returned `HTTP 404: workflow ar-smoke.yml not found on the default branch`, the same behavior WP-RN-01 hit; the push trigger + job gate fixed it (push run `36441528697`, conclusion `skipped`, published nothing).\n- Dispatch: `gh workflow run ar-smoke.yml -R a5c-ai/fabric --ref phase0/hard-split-registry-guards` → run `https://github.com/a5c-ai/fabric/actions/runs/36441575117` (run number 2, attempt 1, head `984e39bde`), conclusion **success**, all steps green.\n- Log excerpts (job `108993127550`):\n\n```text\nRetrieving credentials from gcloud...\nSuccess!\nnpm notice PING https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/\nnpm notice PONG 215ms\nnpm notice Publishing to https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/ with tag latest and default access (dry-run)\n+ @fabricstack/smoke@0.0.0-smoke.2\nnpm notice Publishing to https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/ with tag latest and default access\n+ @fabricstack/smoke@0.0.0-smoke.2\nname = '@fabricstack/smoke'\nversion = '0.0.0-smoke.2'\ndist.tarball = 'https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/@fabricstack/smoke/-/@fabricstack/smoke-0.0.0-smoke.2.tgz'\n```\n\n- Registry state afterwards: `gcloud artifacts packages list --repository=fabric-npm --location=us-central1` → `@fabricstack/smoke` (2026-09-28T18:11:04); versions → `0.0.0-smoke.2` only.\n\n## Local install transcript (AC2 install half; this machine, following only `docs/user-guide/private-install.md`)\n\nFresh directory outside the repository (`gcloud auth login` had already been done for `tal@a5c.ai`; `gcloud auth list` → `tal@a5c.ai *`):\n\n```text\n$ printf '@fabricstack:registry=https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/\\n' > .npmrc\n$ npx -y google-artifactregistry-auth --repo-config=.npmrc --credential-config=\"$HOME/.npmrc\"\nRetrieving application default credentials...\nRetrieving credentials from gcloud...\nSuccess!\n$ npm init -y >/dev/null\n$ npm view @fabricstack/smoke version --registry https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/\n0.0.0-smoke.2\n$ npm install @fabricstack/smoke@0.0.0-smoke.2\nadded 1 package, and audited 2 packages in 2s\nfound 0 vulnerabilities\n$ node -e \"console.log(require('@fabricstack/smoke'))\"\n{ ok: true }\n$ node -e \"const p=require('@fabricstack/smoke/package.json');console.log(p.name, p.version, p.publishConfig.registry)\"\n@fabricstack/smoke 0.0.0-smoke.2 https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/\n$ grep resolved package-lock.json\n\"resolved\": \"https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/@fabricstack/smoke/-/@fabricstack/smoke-0.0.0-smoke.2.tgz\"\n$ grep -iE \"_authToken|password|token=\" .npmrc ; echo $?\n1\n```\n\nThe helper wrote `//us-central1-npm.pkg.dev/boot-464019/fabric-npm/:_authToken=...` into the user `~/.npmrc` and left both project `.npmrc` files untouched.\n\n## AC3: committed `.npmrc` has zero credentials\n\n- `grep -iE \"_authToken|password|token=\" .npmrc` → no match (exit 1), before and after the helper and the workflow run.\n- History secret scan: `git log --all -p -- .npmrc | grep -iE \"_authToken|password|token=\"` → no match across all three historical commits touching the file (`836d2677e`, `d2fd97ff0`, `1f3ea8ad8`).\n- Other `.npmrc` files in the tree: `packages/observer-dashboard/.npmrc` (`engine-strict`, `install-strategy` only). No package sets a conflicting `publishConfig.registry`; the 43 `@a5c-ai/*` packages keep `publishConfig.access: public` untouched (their retarget is WP-RN-04/RN-1).\n- The workflow's credential step asserts the same grep and `git diff --exit-code -- .npmrc` after the helper runs.\n- Gate command (run from the repository root after everything else): `npx -y google-artifactregistry-auth --repo-config=.npmrc >/dev/null 2>&1; npm ping --registry https://us-central1-npm.pkg.dev/boot-464019/fabric-npm/ && ! grep -iE \"_authToken|password|token=\" .npmrc && echo AR-PING-PASS` → `PONG 332ms`, `AR-PING-PASS`.\n\n## Isolation checks\n\n- `curl -s -o /dev/null -w \"%{http_code}\" https://registry.npmjs.org/@fabricstack%2fsmoke` → `404`.\n- `npm view @fabricstack/smoke --registry https://registry.npmjs.org/` from a directory **without** scope routing → `E404 '@fabricstack/smoke@*' is not in this registry`.\n- Caveat for anyone repeating that check: run it from inside the repository and npm answers from Artifact Registry, because a scoped `@fabricstack:registry` line takes precedence over `--registry` for scoped names. That precedence is why the committed `.npmrc` alone already steers every `@fabricstack/*` request away from npmjs; it is not a substitute for the G1–G6 guards of WP-RN-04.\n- `node scripts/ci/public-push-guard.cjs` → `scanned 120 files, 0 finding(s), 0 suppressed`; `--self-test` → `24/24 assertions passed`. The new workflow and fixture add no public-repo reference.\n- `gh api repos/a5c-ai/babysitter --jq .pushed_at` → `2026-09-16T19:59:50Z` at start and end; `git remote -v` → `upstream no_push (push)` throughout.\n\n## Commits\n\n- `393bf1f03` feat(registry): provision Artifact Registry npm + @fabricstack scope routing (WP-RN-03)\n- `984e39bde` ci(registry): register ar-smoke from feature branches via a gated push trigger\n- this record: see `git log -- docs/specs/platform-gaps/phase0/ar-provisioning.md`\n\n## Rollback\n\n```bash\n# remove the smoke package (keeps the repository)\ngcloud artifacts packages delete @fabricstack/smoke --repository=fabric-npm --location=us-central1 --project=boot-464019 --quiet\n# remove the IAM bindings\ngcloud artifacts repositories remove-iam-policy-binding fabric-npm --location=us-central1 --project=boot-464019 \\\n --member=user:tal@a5c.ai --role=roles/artifactregistry.reader\ngcloud artifacts repositories remove-iam-policy-binding fabric-npm --location=us-central1 --project=boot-464019 \\\n --member=serviceAccount:github-deployer@boot-464019.iam.gserviceaccount.com --role=roles/artifactregistry.writer\n# delete the repository (irreversible; deletes every package in it)\ngcloud artifacts repositories delete fabric-npm --location=us-central1 --project=boot-464019 --quiet\n# fabric side\ngit revert 984e39bde 393bf1f03\n# developer machines: drop the token line from ~/.npmrc (tokens expire after about an hour anyway)\n```\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs",
"to": "page:docs-specs-platform-gaps-phase0-ar-provisioning",
"kind": "contains_page"
}
]
}