Skip to content

docs: fix drift from the 0.2 changes and give secrets a page - #37

Merged
refsz merged 3 commits into
mainfrom
docs/pre-0.2-drift
Aug 19, 2026
Merged

docs: fix drift from the 0.2 changes and give secrets a page#37
refsz merged 3 commits into
mainfrom
docs/pre-0.2-drift

Conversation

@refsz

@refsz refsz commented Aug 19, 2026

Copy link
Copy Markdown
Owner

A pass over every doc page against the source, before the 0.2 tag. Every claim below was checked against src/, not against the prose.

Broken for anyone who pastes it

README.md:22 called shellRaw(). #30 removed it. The README's only code sample - the one GitHub and Packagist render - was a guaranteed Error: Call to undefined method. docs/index.md already had the corrected version; the README never got it, and as a result never mentioned exec() at all.

docs/tasks.md promised timeout has no default. It has one: ShellExecutor::DEFAULT_TIMEOUT = 300.0, and TaskContext passes 'timeout' => $options['timeout'] ?? null, so null falls through to 300 seconds rather than disabling the limit. A ten-minute migration would have been killed at five minutes against documentation saying that could not happen. Now states the default and that tty: true is what removes the limit.

docs/tasks.md passed $ctx as log()'s third argument. The signature is log(string $level, string $message, array $context = []); passing a TaskContext is a fatal TypeError.

Secret masking was nearly invisible

It is the feature 0.2 exists for, and it had no page, no nav entry, no index card, and no mention in the README - the string "secret" did not occur in that file. The only prose lived in the middle of variables.md, reachable only from inside another page.

It is now docs/secrets.md:

  • what masking does comes first, with a worked exec() example showing the echoed line masked while curl receives the real token
  • the caveats stay, moved under a heading that frames them honestly: masking is a display filter, not an information-flow guarantee
  • adds the one trap the source implies but nothing stated: type: env is secrets-only. variables.dynamics rejects it (ConfigValidator allows command|git|script|system|composite), so a reader who just learned type: env from the secrets section would hit a validation error.

variables.md keeps a short pointer, and the cross-references in tasks.md, configuration.md and listeners.md follow the move - mkdocs build --strict would fail on a stale anchor, and passes.

Smaller corrections

  • ExecutionResult table was missing command (the property holding the unmasked command line, which the secrets page warns about) and getOutput() / getErrorOutput() / getCombinedOutput().
  • --version and --help were undocumented, while CONTRIBUTING.md tells bug reporters to run sputnik --version.
  • The resolution-priority list did not mention secrets. They sit outside it: a colliding name is a hard config error, so there is no priority to state, and they resolve lazily.
  • docs/environments.md showed sh -c "drush sql-dump | gzip > dump.gz" as if Sputnik echoed those quotes. ShellExecutor::display() joins argv with spaces and makes no quoting promise. The quotes still usefully show the argument boundary, so they stay with a note.
  • Terminal mockups in README.md, docs/index.md and docs/quickstart.md said 0.1.x; the README's also dropped the emoji and the v that SputnikOutput actually prints. Now 🛰 Sputnik v0.2.0.

Checked and deliberately left alone

  • No stale shellRaw anywhere else; the one hit in the tests is a comment noting its removal.
  • No stale {command} placeholder; the one hit is a negative test asserting the old form is rejected.
  • Every executor: in the docs is already a list, and environment.shell is documented and matches DEFAULT_SHELL = ['sh', '-c'].
  • The exec() vs shell() explanation in tasks.md is correct, including the Go-template escaping warning - {{range .items}} genuinely does not match the parser's pattern while {{end}} does.
  • PHP 8.3 is consistent everywhere; the only 8.2 hits are transitive constraints in composer.lock.

🤖 Generated with Claude Code

https://claude.ai/code/session_018CTvnzcNYmFgm2HQcm821A

refsz added 3 commits August 19, 2026 21:01
The README's only code sample called shellRaw(), which 0.2 removed - the sample
GitHub and Packagist render was a guaranteed "Call to undefined method" for
anyone who pasted it. It now uses exec(), and the README never mentioned exec()
at all before.

Secret masking is the feature 0.2 is for, and it had no page, no nav entry, no
index card and no mention in the README: the only prose lived inside
variables.md. It is now docs/secrets.md, in the nav, with what is masked stated
before the caveats rather than as a footnote to another page. variables.md keeps
a short pointer, and the cross-references in tasks.md, configuration.md and
listeners.md follow the move.

Two claims were simply wrong:

- tasks.md promised "default: no timeout" for `timeout`. There is one -
  ShellExecutor::DEFAULT_TIMEOUT is 300 seconds, and `null` falls through to it
  rather than disabling it. A ten-minute migration would have died at five
  minutes against documentation that said it could not.
- tasks.md passed $ctx as log()'s third argument, which is a TypeError. The
  parameter is an array of context data.

Also: the ExecutionResult table was missing `command` and the three getters,
`--version` and `--help` were undocumented while CONTRIBUTING tells bug
reporters to run `--version`, the resolution-priority list did not say secrets
sit outside it, the environments example's comment implied Sputnik echoes quotes
it does not echo, and the version strings in the terminal mockups still said
0.1.x.

`mkdocs build --strict` passes.
Material makes its two sticky nav labels - the section label in the left nav and
the table-of-contents heading - opaque in --md-default-bg-color, with a
same-coloured 0.4rem shadow around them, so nav items scrolling underneath
disappear behind them. That only blends in when the page background is that flat
colour. Ours is an animated three-layer gradient, so both labels showed up as
solid #111318 rectangles with a halo, ending mid-column.

Reproducing the gradient on the labels would work in principle - background-attachment:
fixed resolves percentages against the viewport, so the layers would line up -
but the body background is animated, and any timing difference between the two
animations would show as a moving seam. Frosting needs no such coupling: nothing
to see when only the page is behind the label, and nav items that scroll past
stay legible.

The left label needed the same specificity Material uses for it
(.md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link, four
classes); a shorter selector lost to it and left that one box in place while the
table of contents was already fixed.
The pills name what shapes how Sputnik is used - distribution, contexts,
container routing, the PHP floor. Masking is a guarantee inside one of those,
not a fifth axis, and the feature is already carried by its card in the grid,
the nav entry and its own page.
@refsz
refsz merged commit 5e71dda into main Aug 19, 2026
10 checks passed
refsz added a commit that referenced this pull request Aug 19, 2026
…ed (#40)

The config schema never declared `secrets`. It survived because the `variables`
structure used otherItems(), which accepts any key - so a misspelled section
name was valid config. `secrests:` loaded without a word of complaint, every
secret it declared resolved to null, and the task ran on with an empty argument
and exited 0. Nothing was masked because no value existed. Verified against the
binary before and after:

    before: value=[NULL], "> printf %s token "        (exit 0)
    after:  Unexpected item 'variables > secrests', did you mean 'secrets'?

`secrets` is now declared as a map of either a literal scalar or a provider with
type command|script|env, and `variables` no longer accepts unknown keys. That
also moves an unsupported secret type from first access - inside a task run,
after other work has happened - to config load.

This is a change worth making before 0.2 rather than after: tightening a schema
later rejects configs that load today, which would be a breaking change for
anyone whose config has a typo in it.

Two test configs used a flat `variables: app_name: value` form that the schema
happened to tolerate. The variable resolver never read it - it reads
variables.constants - so those tests were asserting raw config access through a
shape no feature supports. They use constants now.

Also corrects two things in the docs I got wrong in #37: ExecutionResult's
getOutput() and getErrorOutput() return the properties unchanged, not trimmed,
and `timeout: 0` removes the limit just as `tty: true` does (Symfony's
validateTimeout turns 0.0 into null).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant