# whynot-design npm Publish Token Handoff

This is the next-session handoff for `CCR-2026-0001` and the
`whynot-design-npm-publish` access lane.

## Current State

- CCR: `CCR-2026-0001`
- Decision: `e6381a56-6b04-4fd5-b2de-f3ef59cde888`
- Status: active; non-secret OpenBao apply and verification checks passed
- Front door: `ready`, `resolvable=true`
- Positive verification: passed 2026-06-28
- Negative verification: passed 2026-06-28
- Catalog id: `whynot-design-npm-publish`
- Tenant/org: `coulomb`
- Workload/project: `whynot-design`
- Bound IAM group: `whynot-design`
- Secret path: `platform/workloads/coulomb/whynot-design/npm-publish`
- Field: `NPM_AUTH_TOKEN`
- Token source: Forgejo package token for
  `https://forgejo.coulomb.social/api/packages/coulomb/npm/`

The operator reported that the Forgejo token was generated and stored in OpenBao.
Using the temporary operator token only for non-secret infrastructure work, Codex
confirmed that the policy exists, the OIDC role exists with the whynot-design
binding and redirect URIs, the secret metadata has the expected catalog id, and
the `NPM_AUTH_TOKEN` field is present. No secret value was printed, recorded,
or copied into Git, State Hub, chat, or workplans.

On 2026-06-28, the attended positive OIDC login advanced from a missing
`groups` claim to a bound-claim mismatch. That means the role now requests the
`groups` scope correctly, but the authenticating identity is not a member of
`whynot-design`. The `whynot-design` LLDAP group was created and verified.
The intended publisher/verifier identity was later added, positive
verification passed, then `platform-root` was temporarily removed for negative
verification. The negative check passed with a groups bound-claim mismatch, and
`platform-root` was restored to `whynot-design`.

## Safety Rules

- Do not paste `NPM_AUTH_TOKEN` into Git, State Hub, chat, shell history, logs,
  workplans, or screenshots.
- Do not run verification with shell tracing enabled.
- Record only non-secret evidence: path, field name, metadata keys, policy name,
  role name, actor, timestamp, and pass/fail result.
- Mark ops-warden catalog entries ready only after positive and negative
  verification are complete. For this lane, both checks have passed.

## OpenBao Secret Check

In the OpenBao UI, confirm the secret exists under the `platform` KV engine:

```text
workloads/coulomb/whynot-design/npm-publish
```

Expected field:

```text
NPM_AUTH_TOKEN
```

Expected custom metadata:

```text
catalog-id = whynot-design-npm-publish
```

Do not reveal the value during review.

## Policy

Create or update ACL policy:

```text
workload-kv-read-whynot-design-npm-publish
```

Policy body:

```hcl
path "platform/data/workloads/coulomb/whynot-design/npm-publish" {
  capabilities = ["read"]
}

path "platform/metadata/workloads/coulomb/whynot-design/npm-publish" {
  capabilities = ["read"]
}
```

Equivalent CLI command from an approved OpenBao operator context:

```bash
bao policy write workload-kv-read-whynot-design-npm-publish \
  openbao/policies/workload-kv-read-whynot-design-npm-publish.hcl
```

## OIDC Role

Create or update:

```text
auth/netkingdom/role/whynot-design-workload-kv-read
```

Role payload:

```json
{
  "role_type": "oidc",
  "allowed_redirect_uris": [
    "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback",
    "http://localhost:8250/oidc/callback",
    "http://127.0.0.1:8250/oidc/callback"
  ],
  "oidc_scopes": [
    "openid",
    "profile",
    "email",
    "groups"
  ],
  "user_claim": "sub",
  "groups_claim": "groups",
  "bound_claims": {
    "groups": ["whynot-design"]
  },
  "policies": "workload-kv-read-whynot-design-npm-publish",
  "ttl": "15m"
}
```

Equivalent CLI command from an approved OpenBao operator shell:

```bash
role_payload_file="$(mktemp)"
trap 'rm -f "$role_payload_file"' EXIT
cat >"$role_payload_file" <<'JSON'
{
  "allowed_redirect_uris": [
    "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback",
    "http://localhost:8250/oidc/callback",
    "http://127.0.0.1:8250/oidc/callback"
  ],
  "oidc_scopes": [
    "openid",
    "profile",
    "email",
    "groups"
  ],
  "bound_claims": {
    "groups": [
      "whynot-design"
    ]
  },
  "groups_claim": "groups",
  "policies": "workload-kv-read-whynot-design-npm-publish",
  "role_type": "oidc",
  "ttl": "15m",
  "user_claim": "sub"
}
JSON
bao write auth/netkingdom/role/whynot-design-workload-kv-read @"$role_payload_file"
```

The OpenBao Browser CLI cannot run this shell block and may treat
`bound_claims={...}` as a string. When staying in the Web UI, open the API
Explorer and submit the role payload JSON above with:

```text
method: PUT
path: /v1/auth/netkingdom/role/whynot-design-workload-kv-read
```

If the API Explorer asks for a path without the API prefix, use
`auth/netkingdom/role/whynot-design-workload-kv-read`.

## Non-Secret Reads

These commands should succeed from an operator-capable identity and do not print
the token value:

```bash
bao kv metadata get platform/workloads/coulomb/whynot-design/npm-publish
bao policy read workload-kv-read-whynot-design-npm-publish
bao read auth/netkingdom/role/whynot-design-workload-kv-read
```

## Positive Verification

Positive verification proves the approved whynot-design identity can fetch the
field without exposing it in logs.

Before retrying, confirm the account used for OIDC login is a member of the
`whynot-design` LLDAP group. If OpenBao reports:

```text
claim "groups" does not match any associated bound claim values
```

then the groups claim is present, but the account is not in `whynot-design` or
KeyCape did not emit that membership in the fresh login.

The positive verification passed on 2026-06-28. During that run, the CLI printed
the short-lived OpenBao login token; it was revoked immediately by accessor.
Prefer `bao login -no-print` for future attended verification if the installed
CLI accepts that flag.

Use an attended shell, keep tracing disabled, and suppress command output:

```bash
set +x
bao login -no-print -method=oidc -path=netkingdom role=whynot-design-workload-kv-read
bao kv get -format=json platform/workloads/coulomb/whynot-design/npm-publish \
  | jq -e '.data.data.NPM_AUTH_TOKEN | type == "string" and length > 0' \
  >/dev/null
```

Record only that the check passed.

## Negative Verification

Negative verification proves a non-whynot identity cannot read the same field.

Use a non-whynot identity or role and confirm the read is denied. Do not print
or store any token value.

Record only the denial result and non-secret audit timestamp/request metadata.

The negative verification passed on 2026-06-28. `platform-root` was temporarily
removed from `whynot-design`; OpenBao rejected the OIDC login with a groups
bound-claim mismatch, so no OpenBao client token was issued and the secret was
not read. `platform-root` was then restored to `whynot-design`.

## Activation

Only after these are true:

- secret metadata confirmed;
- policy exists and is scoped to the corrected `coulomb/whynot-design` path;
- OIDC role exists and binds only `groups=["whynot-design"]` with approved
  browser/local CLI callback URIs and `groups` OIDC scope;
- positive verification passed;
- negative verification passed;

`CCR-2026-0001` is now `active`, and ops-warden can mark
`whynot-design-npm-publish` `ready`/`resolvable=true`.

Current front door:

```text
readiness = ready
resolvable = true
```
