loki-mode 8.5.2 → 8.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +226 -6
- package/autonomy/run.sh +117 -9
- package/autonomy/telemetry.sh +45 -0
- package/autonomy/verify.sh +85 -19
- package/completions/_loki +1 -0
- package/completions/loki.bash +1 -1
- package/dashboard/__init__.py +1 -1
- package/docs/EVALUATING.md +28 -0
- package/docs/PRIVACY.md +25 -2
- package/docs/STRATEGY-2026-2028.md +124 -0
- package/docs/adoption-baseline-2026-07-31.md +84 -0
- package/docs/air-gapped.md +91 -0
- package/docs/brownfield.md +120 -0
- package/docs/cost-controls.md +88 -0
- package/docs/environment-variables.md +102 -0
- package/docs/exit-codes.md +130 -0
- package/docs/image-provenance.md +124 -0
- package/loki-ts/dist/loki.js +381 -372
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Working on a codebase you already have
|
|
2
|
+
|
|
3
|
+
Most of this category is built for starting from nothing. If you already have a
|
|
4
|
+
repository -- especially a large, private, awkward one -- your options narrow
|
|
5
|
+
fast.
|
|
6
|
+
|
|
7
|
+
## Where the tools actually stand
|
|
8
|
+
|
|
9
|
+
Verified from vendor documentation, 2026-07-31:
|
|
10
|
+
|
|
11
|
+
| Tool | Existing repository |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Lovable | **Cannot import one.** "You can only export from Lovable to GitHub"; two-way sync begins only after Lovable creates the repo. |
|
|
14
|
+
| Replit Agent | Imports GitHub (public and private), Figma, ZIP -- into Replit's environment. |
|
|
15
|
+
| Cursor | Indexes your repo; embeddings are uploaded, obfuscated and encrypted. |
|
|
16
|
+
| Devin | Indexes the repo, plus YAML blueprints producing snapshots each session boots from. |
|
|
17
|
+
| Loki | Runs in place, where the code already is. |
|
|
18
|
+
|
|
19
|
+
That last row is the whole difference, and it is not a preference. For a private
|
|
20
|
+
monorepo with internal dependencies and submodules, "upload it to our
|
|
21
|
+
environment" is often not a thing anyone is permitted to do.
|
|
22
|
+
|
|
23
|
+
**Credit where it is due:** Devin has the strongest documented modernization
|
|
24
|
+
story of the four -- named playbooks for COBOL, Java upgrades, and
|
|
25
|
+
SAS-to-PySpark. If you want a vendor-run modernization program, look at them
|
|
26
|
+
seriously. What follows is what we do differently, not a claim that they are
|
|
27
|
+
bad at this.
|
|
28
|
+
|
|
29
|
+
## Start here: a read-only assessment that costs nothing
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
loki heal ./your-repo --assess --json
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
No provider call, no API key, no spend, no writes. It reports:
|
|
36
|
+
|
|
37
|
+
- a **maturity level** with the reason stated (for example: "No test/spec files
|
|
38
|
+
detected: changes are unguarded")
|
|
39
|
+
- **ranked targets** with blast-radius reasoning per file ("isolated (no inbound
|
|
40
|
+
imports -> low blast radius), 12 LOC")
|
|
41
|
+
- **debt signals**: test ratio, TODO density
|
|
42
|
+
- **the runtime it declares**: Node engine constraint, dependency count, and the
|
|
43
|
+
frameworks actually present in the manifest
|
|
44
|
+
- **dependency lock status**
|
|
45
|
+
|
|
46
|
+
This is the honest opening move: you learn where to start before committing to
|
|
47
|
+
anything.
|
|
48
|
+
|
|
49
|
+
### What it deliberately does not tell you
|
|
50
|
+
|
|
51
|
+
`dependency_staleness` reports `unknown`, always, offline. We know your manifest
|
|
52
|
+
pins lodash 3.x; we do not know what is current upstream without a network call,
|
|
53
|
+
and we will not guess. That refusal is the same reason the assessment works
|
|
54
|
+
inside an air-gapped network at all.
|
|
55
|
+
|
|
56
|
+
## The healing phases
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
loki heal ./your-repo --phase archaeology # map dependencies, catalog friction
|
|
60
|
+
loki heal ./your-repo --phase stabilize # add observability and tests, no behavior change
|
|
61
|
+
loki heal ./your-repo --phase isolate # adapter boundaries between components
|
|
62
|
+
loki heal ./your-repo --phase modernize # replace one component at a time, behind adapters
|
|
63
|
+
loki heal ./your-repo --phase validate # prove behavioral equivalence against the baseline
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
These call a provider and cost money. `--assess` does not.
|
|
67
|
+
|
|
68
|
+
## Behavioral equivalence is the part worth arguing about
|
|
69
|
+
|
|
70
|
+
Every tool in this category will tell you it preserved your business logic.
|
|
71
|
+
Devin's COBOL page says it preserves "critical functionality." What none of them
|
|
72
|
+
document is a *procedure* for proving it.
|
|
73
|
+
|
|
74
|
+
That is the axis we build on:
|
|
75
|
+
|
|
76
|
+
- **characterization tests** capture what the system does today, quirks
|
|
77
|
+
included, before anything is modernized
|
|
78
|
+
- **friction classification** distinguishes accidental mess from load-bearing
|
|
79
|
+
weirdness -- the 30-second sleep that looks stupid and is actually a race-
|
|
80
|
+
condition fix nobody documented
|
|
81
|
+
- **a backward-compatibility auditor** blocks removal of unclassified friction
|
|
82
|
+
- **the validate phase** checks behavior against the recorded baseline
|
|
83
|
+
|
|
84
|
+
Then the [Evidence Receipt](../README.md#the-evidence-receipt-dont-trust-the-agent-check-it)
|
|
85
|
+
records what was proven and what was not, bound to the specific diff.
|
|
86
|
+
|
|
87
|
+
"We prove behavior is unchanged" is a stronger claim than "we preserve business
|
|
88
|
+
logic," and it is the one you can check.
|
|
89
|
+
|
|
90
|
+
The full procedure -- what each phase does, what the friction taxonomy
|
|
91
|
+
distinguishes, and where the safety gates sit -- is in
|
|
92
|
+
[skills/healing.md](../skills/healing.md), with the research it draws on in
|
|
93
|
+
[references/legacy-healing-patterns.md](../references/legacy-healing-patterns.md).
|
|
94
|
+
|
|
95
|
+
### The friction question, concretely
|
|
96
|
+
|
|
97
|
+
The hardest part of a legacy migration is not translating syntax. It is telling
|
|
98
|
+
the difference between:
|
|
99
|
+
|
|
100
|
+
- a `sleep 30` that is genuinely dead weight, and
|
|
101
|
+
- a `sleep 30` that is the only thing preventing a race condition nobody wrote
|
|
102
|
+
down, whose author left in 2019
|
|
103
|
+
|
|
104
|
+
Delete the second and the system breaks in production, weeks later, in a way
|
|
105
|
+
nobody connects to the migration. This is why the auditor blocks removal of
|
|
106
|
+
*unclassified* friction: not because the friction is sacred, but because
|
|
107
|
+
"we do not know what this does yet" is a real state that deserves a name
|
|
108
|
+
instead of a guess.
|
|
109
|
+
|
|
110
|
+
## Honest scope
|
|
111
|
+
|
|
112
|
+
- **Measured and working:** `--assess` on real repositories, verified by
|
|
113
|
+
execution.
|
|
114
|
+
- **Implemented, not measured here:** the five mutating phases need a provider
|
|
115
|
+
and real spend; this page does not claim an end-to-end benchmark we have not
|
|
116
|
+
published.
|
|
117
|
+
- **We do not do COBOL.** If your problem is a mainframe, we are not your
|
|
118
|
+
answer today.
|
|
119
|
+
- **Your code stays put.** Nothing is uploaded to us -- there is no "us" in the
|
|
120
|
+
data path. See [cost controls](./cost-controls.md) for how spend is bounded.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Cost controls
|
|
2
|
+
|
|
3
|
+
You set the ceiling. We stop at it, and the receipt tells you where the money
|
|
4
|
+
went.
|
|
5
|
+
|
|
6
|
+
## Why this page exists
|
|
7
|
+
|
|
8
|
+
"I paid for the AI's own mistakes" is one of the sharpest complaints in this
|
|
9
|
+
category, and it is worth being precise about how our model differs.
|
|
10
|
+
|
|
11
|
+
Most competitors sell credits. Lovable has already addressed the objection
|
|
12
|
+
directly -- their "Try to fix" button does not consume credits, and their
|
|
13
|
+
troubleshooting docs push you toward reverting or replanning instead of
|
|
14
|
+
retrying the same prompt. That is a good policy and we are not claiming to have
|
|
15
|
+
invented a better one.
|
|
16
|
+
|
|
17
|
+
**Our model is different in kind: you bring your own provider credentials.** We
|
|
18
|
+
never bill you, because we are never in the payment path. What we owe you
|
|
19
|
+
instead is a hard ceiling and an honest account of what was spent -- which is
|
|
20
|
+
what this page describes.
|
|
21
|
+
|
|
22
|
+
## The three caps
|
|
23
|
+
|
|
24
|
+
They bound different things, and a run that stalls needs all three.
|
|
25
|
+
|
|
26
|
+
| Cap | Bounds | Default |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `LOKI_BUDGET_LIMIT` | Total spend, in USD | unset (no cap) |
|
|
29
|
+
| `LOKI_MAX_ITERATIONS` | Number of iterations | 1000 |
|
|
30
|
+
| `LOKI_MAX_DURATION` | Wall-clock time | unset (no cap) |
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
LOKI_BUDGET_LIMIT=25 loki start ./prd.md
|
|
34
|
+
loki start ./prd.md --max-duration 90m
|
|
35
|
+
loki config set budget 25
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Why three and not one.** Spend and iterations both assume forward progress. A
|
|
39
|
+
run that *stalls* -- a hung provider call, a wedged subprocess -- burns hours
|
|
40
|
+
while spending almost nothing and completing no iteration, so neither of those
|
|
41
|
+
breakers ever trips. The wall-clock cap is the one that catches it. We added it
|
|
42
|
+
after a run burned $34 reaching an external timeout.
|
|
43
|
+
|
|
44
|
+
## Hitting a cap is a FAILURE, not a success
|
|
45
|
+
|
|
46
|
+
This is the part that matters for anyone automating against us.
|
|
47
|
+
|
|
48
|
+
All three caps produce a **terminal failure**: exit 20 under
|
|
49
|
+
`LOKI_DURABLE_STATE=1`, with a distinct status (`budget_exceeded`,
|
|
50
|
+
`max_iterations_reached`, `max_duration_reached`) so the receipt and `loki why`
|
|
51
|
+
can tell you *which* ceiling you hit and therefore which one to raise.
|
|
52
|
+
|
|
53
|
+
`budget_exceeded` used to exit **0**, on the reasoning that a human would raise
|
|
54
|
+
the cap and resume. That is true at a terminal and false inside a Kubernetes
|
|
55
|
+
Job, where there is no human: the Job went Complete, the pipeline went green,
|
|
56
|
+
and an incomplete build looked finished. Exit 0 now means the work is finished
|
|
57
|
+
or a person deliberately stopped it -- never that we ran out of money mid-task.
|
|
58
|
+
|
|
59
|
+
See [exit codes](./exit-codes.md) for the full contract.
|
|
60
|
+
|
|
61
|
+
## Preview the cost before spending anything
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
loki plan ./prd.md --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Reports complexity, estimated iterations, token usage and cost **without
|
|
68
|
+
executing**. No provider call, no spend. `LOKI_CONFIG_DUMP=1 loki start ./prd.md`
|
|
69
|
+
prints the resolved configuration and exits, so you can confirm your caps are
|
|
70
|
+
actually set before a real run.
|
|
71
|
+
|
|
72
|
+
## What you are charged for, stated plainly
|
|
73
|
+
|
|
74
|
+
Every iteration calls your provider, including iterations spent re-running
|
|
75
|
+
after a quality gate fails. **We do not exempt our own gate failures from your
|
|
76
|
+
budget**, and pretending otherwise would be dishonest -- those calls really do
|
|
77
|
+
consume your tokens.
|
|
78
|
+
|
|
79
|
+
What we do instead:
|
|
80
|
+
|
|
81
|
+
- the caps above bound the total, so a doom loop has a hard ceiling
|
|
82
|
+
- the Evidence Receipt records iteration count and cost, so a re-run is visible
|
|
83
|
+
rather than buried
|
|
84
|
+
- reaching a cap reports as a failure with the reason named, so you know whether
|
|
85
|
+
to raise the ceiling or narrow the spec
|
|
86
|
+
|
|
87
|
+
If a run cost more than you expected, `loki proof show <id>` tells you where it
|
|
88
|
+
went.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Environment variables
|
|
2
|
+
|
|
3
|
+
The operator-facing knobs, with defaults read from the source rather than from
|
|
4
|
+
memory. Every variable on this page is asserted by a test to still exist, so
|
|
5
|
+
this document cannot quietly rot into fiction.
|
|
6
|
+
|
|
7
|
+
## What this page is not
|
|
8
|
+
|
|
9
|
+
The codebase references a few hundred `LOKI_*` tokens. Most are internal
|
|
10
|
+
plumbing between the CLI and the runner (`LOKI_REG_TARGET`,
|
|
11
|
+
`LOKI_CI_JSON_FINDINGS`, and similar), or prefix fragments that are never a
|
|
12
|
+
whole variable. Publishing that number as an "operator surface" would be
|
|
13
|
+
misleading, so this page covers only what an operator would deliberately set.
|
|
14
|
+
|
|
15
|
+
`loki config schema` lists 64 more keys that map to environment variables and
|
|
16
|
+
can be written to `.loki/config.yaml`. Those are not repeated here. The
|
|
17
|
+
variables below are the ones **not** in that schema -- historically the ones
|
|
18
|
+
with nowhere to look them up.
|
|
19
|
+
|
|
20
|
+
## Running a build
|
|
21
|
+
|
|
22
|
+
| Variable | Default | Effect |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `LOKI_PRD_FILE` | none | Path to a spec file, as an alternative to the positional argument. |
|
|
25
|
+
| `LOKI_MAX_ITERATIONS` | `1000` | Hard cap on iterations. Reaching it is a deterministic terminal failure, not a success. Also in `config schema`. |
|
|
26
|
+
| `LOKI_BUDGET_LIMIT` | unset (no cap) | Spend cap in USD. On exhaustion the run stops and reports a terminal failure -- see [exit codes](./exit-codes.md). |
|
|
27
|
+
| `LOKI_MAX_DURATION` | unset (no cap) | Wall-clock cap in seconds. Stops cleanly at the next iteration boundary with a `max_duration_reached` terminal status. `loki start --max-duration` also accepts `90m` / `2h`. |
|
|
28
|
+
| `LOKI_AUTO_CONFIRM` | unset | `true`/`false` to control prompts. Takes precedence over `CI`. |
|
|
29
|
+
| `LOKI_CONFIG_DUMP` | `0` | `1` prints the resolved configuration and exits **without starting a run or spending anything**. |
|
|
30
|
+
|
|
31
|
+
For a cost estimate before committing to a run, `loki plan <spec> --json` is
|
|
32
|
+
the better tool: it reports complexity, iterations, tokens and cost without
|
|
33
|
+
executing. See [cost controls](./cost-controls.md) for how the three caps
|
|
34
|
+
interact and why hitting one is a failure rather than a success.
|
|
35
|
+
|
|
36
|
+
## Choosing a model and provider
|
|
37
|
+
|
|
38
|
+
| Variable | Default | Effect |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `LOKI_PROVIDER` | `claude` | `claude`, `cline`, `codex`, or `aider`. |
|
|
41
|
+
| `LOKI_MAX_TIER` | unlimited | Caps model tier, so a run cannot escalate past what you are willing to pay for. |
|
|
42
|
+
| `LOKI_TIER` | per-phase default | Forces a specific tier for the run. |
|
|
43
|
+
| `LOKI_SESSION_MODEL` | provider default | Pins the session model explicitly. |
|
|
44
|
+
| `LOKI_MODEL_OVERRIDE` | unset | Overrides the resolved model outright. |
|
|
45
|
+
|
|
46
|
+
`LOKI_MAX_TIER` is the cost control worth knowing: it bounds escalation, while
|
|
47
|
+
`LOKI_BUDGET_LIMIT` bounds total spend. They answer different questions and are
|
|
48
|
+
usefully set together.
|
|
49
|
+
|
|
50
|
+
## Output volume
|
|
51
|
+
|
|
52
|
+
| Variable | Default | Effect |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `LOKI_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, or `error`. |
|
|
55
|
+
| `LOKI_QUIET` | `0` | `1` is shorthand for `warn`. |
|
|
56
|
+
|
|
57
|
+
**Errors are never suppressed.** `error` is the floor, so even the quietest
|
|
58
|
+
setting still prints failures -- a verbosity control that could hide why a
|
|
59
|
+
build failed would be a footgun. An unrecognized value falls back to `info`
|
|
60
|
+
rather than silencing the run, so a typo in a pipeline config cannot blind you.
|
|
61
|
+
|
|
62
|
+
Available as flags too: `loki start --quiet`, `loki start --log-level LEVEL`.
|
|
63
|
+
|
|
64
|
+
Decorative output (the HUD, the completion card, the start headline) already
|
|
65
|
+
suppresses itself when stdout is not a TTY, so CI logs were never the wall of
|
|
66
|
+
banners you might expect. These variables control the remaining `[INFO]` and
|
|
67
|
+
`[STEP]` lines.
|
|
68
|
+
|
|
69
|
+
## Platform integration
|
|
70
|
+
|
|
71
|
+
| Variable | Default | Effect |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `LOKI_DURABLE_STATE` | `0` | `1` enables durable state **and** the richer process-exit contract that lets Kubernetes distinguish a deterministic failure from a crash. See [exit codes](./exit-codes.md). |
|
|
74
|
+
| `LOKI_SDK_LOOP` | unset | Routes the run through the Bun/TypeScript runner instead of bash. Both implement the same exit contract. |
|
|
75
|
+
|
|
76
|
+
`LOKI_DURABLE_STATE=1` is the one to set in a Job or task definition. Without
|
|
77
|
+
it every failure collapses to exit 1 and the platform cannot tell "re-running
|
|
78
|
+
this is pointless" from "this crashed and should resume".
|
|
79
|
+
|
|
80
|
+
## Precedence
|
|
81
|
+
|
|
82
|
+
Command-line flags beat environment variables, which beat `.loki/config.yaml`,
|
|
83
|
+
which beats `~/.config/loki-mode/config.yaml`.
|
|
84
|
+
|
|
85
|
+
To see what actually resolved, without starting a run:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
LOKI_CONFIG_DUMP=1 loki start ./prd.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Setting these persistently
|
|
92
|
+
|
|
93
|
+
`loki config set` writes to the config file for the keys it supports:
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
loki config set budget 25
|
|
97
|
+
loki config set provider claude
|
|
98
|
+
loki config set maxTier sonnet
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`loki config schema` lists every key the config file understands, and
|
|
102
|
+
`loki config show` prints the current effective values.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Exit codes
|
|
2
|
+
|
|
3
|
+
Every code below was read from the source, not from a help text. If a command
|
|
4
|
+
is absent from this page it returns only the shell defaults (0 on success,
|
|
5
|
+
nonzero on failure) and you should not build a gate on anything finer.
|
|
6
|
+
|
|
7
|
+
## The one rule
|
|
8
|
+
|
|
9
|
+
**Severity rises with the code.** `[ $rc -ge 2 ]` always means "worse than the
|
|
10
|
+
level-1 outcome" for any command on this page. If you remember nothing else,
|
|
11
|
+
remember that a bigger number is never better news.
|
|
12
|
+
|
|
13
|
+
## `loki start`
|
|
14
|
+
|
|
15
|
+
Two contracts, and which one you get depends on an environment variable. This
|
|
16
|
+
is the part most likely to surprise a script author.
|
|
17
|
+
|
|
18
|
+
### Default (local, CI)
|
|
19
|
+
|
|
20
|
+
| Code | Meaning |
|
|
21
|
+
|---|---|
|
|
22
|
+
| 0 | The run completed |
|
|
23
|
+
| nonzero | Something went wrong |
|
|
24
|
+
|
|
25
|
+
There is no finer signal. A gate that needs to tell "failed the quality gate"
|
|
26
|
+
from "crashed" must opt into the durable contract below.
|
|
27
|
+
|
|
28
|
+
### With `LOKI_DURABLE_STATE=1` (the platform contract)
|
|
29
|
+
|
|
30
|
+
Written for a Kubernetes Job, an ECS task, or a systemd unit that has to decide
|
|
31
|
+
whether retrying is worth anything. The distinction it draws is **will running
|
|
32
|
+
this again produce a different result**.
|
|
33
|
+
|
|
34
|
+
| Code | Meaning | Should the platform retry? |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| 0 | Completed, or a human stopped it (council approved, completion promise, force-stop, paused, interrupted, stopped) | No. It is done, or a person is driving. |
|
|
37
|
+
| 20 | Deterministic terminal failure (failed a gate, max iterations, max retries, **budget exceeded**, **wall-clock cap reached**, policy blocked, contradictory spec) | **No.** The same inputs fail the same way; a retry only spends money to arrive here again. |
|
|
38
|
+
| any other nonzero | Crash (SIGKILL, eviction, node loss) | Yes. The restarted run resumes from the durable volume. |
|
|
39
|
+
|
|
40
|
+
`budget_exceeded` sits with the failures deliberately. It used to exit 0 on the
|
|
41
|
+
reasoning that a human would raise the cap and resume, which is true at a
|
|
42
|
+
terminal and false inside a Job: there is no human, so a build stopped mid-work
|
|
43
|
+
by the cost breaker was reported as a success. Exit 0 must mean the work is
|
|
44
|
+
finished or a person chose to stop it.
|
|
45
|
+
|
|
46
|
+
Helm wires this up for you: `worker.exitCodes.terminalFailure` (default 20)
|
|
47
|
+
feeds the Job's `podFailurePolicy`, so a deterministic failure fails the Job
|
|
48
|
+
immediately instead of burning `backoffLimit`. Requires Kubernetes 1.31+.
|
|
49
|
+
|
|
50
|
+
Both the bash runner and the Bun runner (`LOKI_SDK_LOOP`) implement this
|
|
51
|
+
identically; a parity test asserts they agree status for status.
|
|
52
|
+
|
|
53
|
+
## `loki verify`
|
|
54
|
+
|
|
55
|
+
| Code | Verdict |
|
|
56
|
+
|---|---|
|
|
57
|
+
| 0 | VERIFIED |
|
|
58
|
+
| 1 | CONCERNS (findings below the block threshold, or inconclusive evidence) |
|
|
59
|
+
| 2 | BLOCKED (findings at or above the block threshold) |
|
|
60
|
+
| 3 | Verifier error: it could not complete, and never silently passes |
|
|
61
|
+
|
|
62
|
+
Code 3 matters more than it looks. A verifier that cannot run is not a pass,
|
|
63
|
+
so `[ $rc -eq 0 ]` is the only safe test for "verified" -- `[ $rc -ne 2 ]`
|
|
64
|
+
would treat a broken verifier as acceptable.
|
|
65
|
+
|
|
66
|
+
An early draft spec listed `1=BLOCKED, 2=CONCERNS`. That ordering was rejected:
|
|
67
|
+
it is not used anywhere, it has no consumers, and it inverts the
|
|
68
|
+
severity-rises-with-the-code rule that every other command follows.
|
|
69
|
+
|
|
70
|
+
## `loki ci`
|
|
71
|
+
|
|
72
|
+
| Code | Meaning |
|
|
73
|
+
|---|---|
|
|
74
|
+
| 0 | Passed, or all findings are below `--fail-on` |
|
|
75
|
+
| 1 | Findings exceed the `--fail-on` threshold |
|
|
76
|
+
| 2 | Error: missing tools or invalid arguments |
|
|
77
|
+
|
|
78
|
+
Machine-readable output is `--format json` here, not `--json`.
|
|
79
|
+
|
|
80
|
+
## `loki doctor`
|
|
81
|
+
|
|
82
|
+
| Code | Meaning |
|
|
83
|
+
|---|---|
|
|
84
|
+
| 0 | Every required check passed. Optional warnings do not fail the command. |
|
|
85
|
+
| nonzero | At least one required check failed; the output names which |
|
|
86
|
+
|
|
87
|
+
Safe as a preflight gate in an init container or pipeline step:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
loki doctor || { echo "host is not ready"; exit 1; }
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
An absent optional provider CLI is a warning, not a blocker, so this will not
|
|
94
|
+
refuse to start over a tool you were never going to use.
|
|
95
|
+
|
|
96
|
+
## Security scan
|
|
97
|
+
|
|
98
|
+
| Code | Meaning |
|
|
99
|
+
|---|---|
|
|
100
|
+
| 0 | No high or critical findings |
|
|
101
|
+
| 1 | At least one HIGH |
|
|
102
|
+
| 2 | At least one CRITICAL |
|
|
103
|
+
|
|
104
|
+
## Signals
|
|
105
|
+
|
|
106
|
+
`loki start` handles the usual terminating signals conventionally: 130 for
|
|
107
|
+
SIGINT (Ctrl-C), 143 for SIGTERM. Under the durable contract these are crashes
|
|
108
|
+
in the retryable sense -- a pod terminated by the scheduler resumes rather than
|
|
109
|
+
being treated as a completed build.
|
|
110
|
+
|
|
111
|
+
## Writing a gate
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
# Block a merge unless verification is clean. Note -ne 0, not -eq 2:
|
|
115
|
+
# a verifier ERROR (3) must not pass.
|
|
116
|
+
loki verify || { echo "not verified"; exit 1; }
|
|
117
|
+
|
|
118
|
+
# Kubernetes: let the platform decide whether to retry.
|
|
119
|
+
LOKI_DURABLE_STATE=1 loki start ./prd.md
|
|
120
|
+
rc=$?
|
|
121
|
+
case $rc in
|
|
122
|
+
0) echo "complete" ;;
|
|
123
|
+
20) echo "terminal failure -- fix the spec or raise the budget, then re-submit" ;;
|
|
124
|
+
*) echo "crashed (rc=$rc) -- resume is safe" ;;
|
|
125
|
+
esac
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Do not read an exit code through a pipe. `$?` after a pipeline reports the last
|
|
129
|
+
stage, so `loki verify | tee log` gives you `tee`'s status and always looks
|
|
130
|
+
successful. Use `${PIPESTATUS[0]}` in bash, or capture first and test after.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Verifying the container image
|
|
2
|
+
|
|
3
|
+
Everything on this page is a command you run against the public registry and
|
|
4
|
+
the public sigstore transparency log. None of it requires an account with us,
|
|
5
|
+
and none of it trusts anything we say here: if a command below fails, the
|
|
6
|
+
correct conclusion is that the image is not what we claim, not that the
|
|
7
|
+
instructions are stale.
|
|
8
|
+
|
|
9
|
+
## What is covered, and from which version
|
|
10
|
+
|
|
11
|
+
Read this section before the commands. Provenance that claims more than it
|
|
12
|
+
delivers is worse than none, because it is exactly what an auditor will test.
|
|
13
|
+
|
|
14
|
+
| Artifact | Signature | SBOM |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| Container image (`asklokesh/loki-mode`) | first release after v8.5.2 | first release after v8.5.2 |
|
|
17
|
+
| npm tarball (`loki-mode`) | v7.4.10 and later | v7.4.10 and later |
|
|
18
|
+
|
|
19
|
+
**Images published up to and including v8.5.2 are not signed and have no SBOM.** A signing
|
|
20
|
+
workflow existed from v7.4.10 but was triggered on `release: published`, and
|
|
21
|
+
because our releases are created by a workflow using the default
|
|
22
|
+
`GITHUB_TOKEN`, GitHub never emitted that event. The workflow therefore never
|
|
23
|
+
ran on a real release, which the registry confirms: no `sha256-*.sig` tags
|
|
24
|
+
existed for any version through v8.5.2. Signing now runs inside the same job
|
|
25
|
+
that pushes the image, so it cannot be skipped by a trigger that does not fire.
|
|
26
|
+
|
|
27
|
+
We are not backfilling signatures for older tags. A signature applied today by
|
|
28
|
+
a different pipeline than the one that built the artifact months ago attests
|
|
29
|
+
to far less than it appears to, and the appearance is the dangerous part.
|
|
30
|
+
|
|
31
|
+
## Verify the signature
|
|
32
|
+
|
|
33
|
+
Requires [cosign](https://github.com/sigstore/cosign) v2.x. Substitute your
|
|
34
|
+
version for `8.6.0`.
|
|
35
|
+
|
|
36
|
+
Signatures are bound to the image **digest**, not to a tag. Tags are mutable --
|
|
37
|
+
`latest` moves every release, and even a version tag can be repointed -- so
|
|
38
|
+
resolve the digest first and verify that.
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
VERSION=8.6.0 # or whichever release first carried signing; see the table above
|
|
42
|
+
DIGEST=$(docker buildx imagetools inspect "asklokesh/loki-mode:${VERSION}" \
|
|
43
|
+
--format '{{json .Manifest.Digest}}' | tr -d '"')
|
|
44
|
+
echo "$DIGEST"
|
|
45
|
+
|
|
46
|
+
cosign verify \
|
|
47
|
+
--certificate-identity-regexp 'https://github.com/asklokesh/loki-mode/.github/workflows/release.yml@.*' \
|
|
48
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
49
|
+
"asklokesh/loki-mode@${DIGEST}"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The two `--certificate-*` flags are the part that matters, and omitting them
|
|
53
|
+
is the most common way to get a meaningless pass. Keyless signing means anyone
|
|
54
|
+
with a GitHub account can produce a valid sigstore signature over our image;
|
|
55
|
+
what makes the signature *ours* is that it was issued to our workflow identity
|
|
56
|
+
by GitHub's OIDC issuer. Without those flags cosign will happily confirm that
|
|
57
|
+
the image was signed by somebody.
|
|
58
|
+
|
|
59
|
+
A pass prints a JSON block including the certificate's subject, which should
|
|
60
|
+
name `release.yml` in this repository.
|
|
61
|
+
|
|
62
|
+
## Verify and read the SBOM
|
|
63
|
+
|
|
64
|
+
The SBOM is attached to the image as a CycloneDX attestation. Note this is a
|
|
65
|
+
*separate* artifact from the npm SBOM published on the GitHub Release: the
|
|
66
|
+
image carries an OS layer, a Python runtime and system packages that the npm
|
|
67
|
+
tarball's SBOM never described. If you are deploying by Helm, ECS, or any
|
|
68
|
+
other image-based path, the image SBOM is the one that matches what you run.
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
cosign verify-attestation \
|
|
72
|
+
--type cyclonedx \
|
|
73
|
+
--certificate-identity-regexp 'https://github.com/asklokesh/loki-mode/.github/workflows/release.yml@.*' \
|
|
74
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
|
|
75
|
+
"asklokesh/loki-mode@${DIGEST}" \
|
|
76
|
+
| jq -r '.payload' | base64 -d | jq '.predicate' > image-sbom.cdx.json
|
|
77
|
+
|
|
78
|
+
jq '.components | length' image-sbom.cdx.json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`verify-attestation` checks the signature over the SBOM before you read it,
|
|
82
|
+
which is the point: an SBOM you downloaded unverified tells you what someone
|
|
83
|
+
wanted you to believe is in the image.
|
|
84
|
+
|
|
85
|
+
**Scope limit worth knowing.** We publish a multi-arch image (linux/amd64 and
|
|
86
|
+
linux/arm64). The signature covers the manifest list, so it covers both
|
|
87
|
+
architectures. The SBOM does not: it is generated from a single resolved
|
|
88
|
+
platform, so package versions specific to the other architecture may differ
|
|
89
|
+
from what it lists. If you deploy on arm64 and need an exact bill of materials
|
|
90
|
+
for that architecture, generate it against the arch-specific digest yourself:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
syft "asklokesh/loki-mode:${VERSION}" --platform linux/arm64 -o cyclonedx-json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Feed `image-sbom.cdx.json` to Grype, Trivy, Dependency-Track or any other
|
|
97
|
+
CycloneDX consumer for CVE scanning.
|
|
98
|
+
|
|
99
|
+
## Air-gapped environments
|
|
100
|
+
|
|
101
|
+
Both commands above reach the public sigstore infrastructure (Rekor and
|
|
102
|
+
Fulcio) to check the transparency log. To verify inside an air-gapped network,
|
|
103
|
+
mirror the trust root and the artifacts on a connected host first:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
cosign save "asklokesh/loki-mode@${DIGEST}" --dir ./loki-image-bundle
|
|
107
|
+
cosign initialize --mirror <your-tuf-mirror> --root <your-root.json>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Then transfer `loki-image-bundle/` and verify with `cosign verify --local-image
|
|
111
|
+
./loki-image-bundle`. See `loki doctor --airgap` for the full host inventory
|
|
112
|
+
the engine itself needs at runtime.
|
|
113
|
+
|
|
114
|
+
## If verification fails
|
|
115
|
+
|
|
116
|
+
Do not deploy the image. In order of likelihood:
|
|
117
|
+
|
|
118
|
+
1. **You verified a tag instead of a digest.** Re-resolve the digest.
|
|
119
|
+
2. **You omitted the `--certificate-*` flags**, verified a digest that was
|
|
120
|
+
never ours, and got a pass or a confusing mismatch. Both flags are required.
|
|
121
|
+
3. **The version is v8.5.2 or earlier.** See the coverage table above; those images
|
|
122
|
+
are genuinely unsigned and no command will make them verify.
|
|
123
|
+
4. **The image is not what we published.** Report it at
|
|
124
|
+
https://github.com/asklokesh/loki-mode/issues and do not run it.
|