Skip to content

Secrets contract, c op / c secrets commands, and c secrets init - #102

Open
smebberson wants to merge 50 commits into
masterfrom
secrets-contract
Open

smebberson wants to merge 50 commits into
masterfrom
secrets-contract

Conversation

@smebberson

@smebberson smebberson commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

Codifies secrets management per the ras-rsc GSM unification program (docs/secrets-gsm-unification.md). c kc manifests|apply|start now generate SecretProviderClasses and SecretSyncs (Kubernetes manifests files that manage syncing from Google Secret Manager to Kubernetes), the secret-sync ServiceAccount and local Secrets (1Password references) from the contract — written straight to the compiled folder. A conflicting committed file is a hard error; no contract means manifest behaviour is unchanged. README (Commands + Secrets configuration) and CHANGELOG added.

What's here

  • Contract lib (bin/lib/c-secrets.js): secrets.services + sibling secrets.shared (segment-less ids; gsmSegment removed from the schema), unique-owner sharedKeys resolution across services ∪ shared with dotted owner.KEY disambiguation, and opItem references (services.<name> / dev, one hop) so each 1Password item id appears exactly once. Lazy opItem resolution: services resolve-or-create by title; shared never auto-creates.
  • Commands: c op get|set, c secrets get|push|add|verify|ls|consumers|init. Positional identity args, no defaults; values display as truncated hashes only (--plaintext is TTY-gated, agent-env-blocked, discouraged --force); stdin-pipe upserts; push auto-verifies the GSM readback by hash; add is ordered-idempotent-rollback across c.js + 1Password + GSM and never touches the cluster; consumers is the deletion gate.
  • c secrets init (requires a warm 1Password session): drafts the contract from existing declarations — SecretProviderClasses, local templates, --set-secrets, env.*.yaml, docker build args — with value-verified shared.opItem binding and legacy-duplicate removal by hash, sharedKeys authored automatically, and plain-language review notes.
  • c secrets verify: structure + op/GSM/k8s hash reconciliation with storage visibility (stored in the site item), stale-field detection (op:EXTRA), and deployed-functions drift (STALE).
  • Friendly pre-flights: assertOpSession, assertGcloudAuth, assertKubectl — fail fast with actionable errors before any work.

Verified live against ras-rsc: the warm init draft converges on the hand-authored contract in the unification doc (shared auto-bound by value hash, legacy duplicate dropped, bare sharedKeys), and verify beta is hash-green across all keys with only the two known STALE legacy references.

Status

6.2.0-beta.1 and 6.2.0-beta.2 published with the beta dist-tag. ras-rsc has adopted the contract end-to-end (see idearium/ras-rsc#454) — init's warm draft converged on the hand-authored contract, generation proved byte-identical against the committed manifests, and verify beta is hash-green.

Stays draft until the ras-rsc production conversion (Step D) proves the tooling in both environments — then merges and promotes to 6.2.0.

Related PRs:

  • idearium/ras-rsc#454 — beta cutover + contract adoption + manifest deletion (the consuming PR)
  • idearium/devspace#57 — the better-secrets skill rewrite

…crets init.

- Contract lib: secrets.services + sibling secrets.shared (segment-less
  ids, no gsmSegment), unique-owner sharedKeys across services ∪ shared,
  dotted owner.KEY disambiguation, opItem references (services.<name>/dev,
  one hop - each item id appears exactly once), lazy opItem resolution.
- Commands: c op get/set, c secrets get/push/add/verify/ls/consumers/init.
  Values only ever display as truncated hashes; --plaintext is TTY-gated
  and agent-blocked with a discouraged --force.
- init drafts the contract from existing declarations (warm session
  required): value-verified shared.opItem binding and legacy-duplicate
  removal by hash, sharedKeys authored automatically, plain-language
  review notes.
- verify reconciles structure + op/GSM/k8s hashes (storage visibility,
  op:EXTRA stale fields) + functions drift (STALE).
- Friendly pre-flights: assertOpSession, assertGcloudAuth, assertKubectl.
- New bin/lib/c-secrets-manifests.js: generates SecretProviderClasses,
  SecretSyncs and the secret-sync ServiceAccount for deployed
  environments, and local Secrets (op references) for local, directly
  into the compiled folder. A conflicting committed file is a hard error
  (one true source); without a contract, manifest behaviour is unchanged.
- Wired into c kc manifests/apply/start before the file checks;
  ensureServiceFilesExist accepts already-compiled (generated) paths.
- Local Secrets follow the exact pipeline committed templates use:
  op inject, base64-encoded data, 0600 permissions.
- The op helpers (whoami/inject) are now session-aware: the op-session
  alias' session is wired in when present, falling back to the shell
  environment.
- README: Commands summary + a Secrets configuration section.
  CHANGELOG: v6.2.0 entry.
- Exported opItemFields from c-secrets-io: without it, verify's op:EXTRA
  scan silently no-oped (the per-item catch swallowed the undefined
  function), so stale-field findings could never surface.
- ensureServiceFilesExist now accepts an existing compiled file for
  contract-generated manifests (no committed source exists). The
  original version of this check failed to apply and compiled envs
  errored on secretsync.serviceaccount.
@smebberson
smebberson marked this pull request as ready for review October 1, 2026 10:51
@smebberson
smebberson requested a review from allanchau October 1, 2026 10:51

@allanchau allanchau left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smebberson A couple of changes here

Comment thread bin/lib/c-secrets-io.js
Comment thread bin/lib/c-secrets-manifests.js
Comment thread bin/c-op-get.js Outdated
Comment thread bin/c-op-set.js Outdated
Comment thread bin/lib/c-secrets-verify.js Outdated
Comment thread bin/lib/c-secrets-init.js Outdated
Signs in via op signin --raw and stores the raw token at
~/.local/state/idearium/op-session (0600, enforced on overwrite) where
every c command reads it. No account-specific OP_SESSION_<id> env var
names anywhere.
Spawns op with --session <token> (array form, no shell) and inherits
stdio, pre-flighting the session with assertOpSession for a friendly
error. Exit code propagates for scripting.
runOp and the c kc injection pipeline now pass the c-managed session
via --session in the args array instead of injecting an
OP_SESSION_idearium env var, and every error hint points at c op
session. Env intake is unchanged (still checked first) so eval
$(op signin) keeps working.
setOpItemInCjs and addKeyToServiceInCjs matched the first name: { block
anywhere in c.js, so a service named after a docker location (site,
static) silently edited the wrong block. Block location is now scoped
to secrets -> services, and every edit (register, addKey, setOpItem) is
re-parsed from disk and verified against the parsed contract - a miss
or syntax break restores the original text and throws.
resolveServiceKey resolves opRef: null for unbound services and shared
entries - rendering embedded a literal null that survived op inject and
was base64-encoded into the Secret. Generation now fails with a remedy
(c op set for services, shared.opItem for shared keys) instead.
resolveOwnerItem always created for unbound services, so the read path
left stray empty items behind failed reads. resolveOwnerItem now takes
create (default true, shared never creates), and c op get passes
create false per its stated reads-never-create design.
A bare -l parsed to true then NaN into randomBytes (raw crypto error),
and c secrets add had the same latent bug. Both commands now share
generateValueForProgram, which fails with a friendly error for a bare
or invalid -l/-t before generating.
The per-key k8s hash was printed with a hard-coded ok: true - only the
key set was reconciled, so verify reported VERIFIED even when a synced
Secret's value had drifted. Phase 1 now records each key's GSM hash by
secret id and phase 2 compares, flagging drift (or sync lag) instead.
Segment-ness now derives from the owner name (shared is reserved for
the shared block), so the flag carried no information. init's dead
segmentless emission went with it, and c secrets add rejects a new
service named shared up front.
Commander's git-style dispatch re-parses subcommand args at every
layer before spawning, silently dropping -- and unknown options
(breaking e.g. c op cmd run -- sh -c ... and c kc cmd commands
carrying kubectl flags). The op and kc subtrees are now dispatched
with the verbatim argv, and each carries its cmd passthrough down
the same way.
Each phase prints a heading with a one-line explanation and its
output streams as checks run. Data is prefetched in bulk (one item
list, one item fetch per distinct 1Password item, one GSM list, GSM
values with bounded concurrency, one kubectl fetch per synced secret),
and function bindings are also checked against the GSM list, so a
binding referencing a deleted secret is reported (MISSING) alongside
contract drift (STALE).
@smebberson

Copy link
Copy Markdown
Member Author

@allanchau, great job on surfacing those issues. I've resolved them all and reworked the c secrets command quite a bit. Can you please re-review this?

@allanchau allanchau left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smebberson A few more things were picked up, but nothing major.

Comment thread bin/lib/c-secrets.js
Comment thread bin/lib/c-secrets-io.js Outdated
Comment thread bin/lib/c-secrets-io.js Outdated

@allanchau allanchau left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smebberson I think this looks good now.

@smebberson

Copy link
Copy Markdown
Member Author

@allanchau, this is the final thing I've adding - environment binding. It means everything will be named {name}-{env} - even for production, which we haven't done before. It will allow the secrets contract to better determine what lives where. Can you please take another look at the last three commits - but mostly 6b5e828.

Comment thread bin/lib/c-secrets-io.js Outdated

@allanchau allanchau left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@smebberson Just a small change here.

This branch was successfully deployed

1 active deployment
Publish — cc7c41c4 Deployed Oct 22, 2026 by smebberson via Publish #15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants