@ia-qa/self-healing 1.18.0 → 1.19.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 +98 -1
- package/TUTORIAL.md +58 -0
- package/dist/ai/models.d.ts +12 -0
- package/dist/ai/models.js +33 -1
- package/dist/ai/models.js.map +1 -1
- package/dist/ai/resolver.d.ts +29 -6
- package/dist/ai/resolver.js +42 -7
- package/dist/ai/resolver.js.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/diff.js +34 -10
- package/dist/cli/diff.js.map +1 -1
- package/dist/cli/fix.js +9 -2
- package/dist/cli/fix.js.map +1 -1
- package/dist/cli/index.js +47 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/map.d.ts +15 -4
- package/dist/cli/map.js +66 -13
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/secret.d.ts +1 -0
- package/dist/cli/secret.js +172 -0
- package/dist/cli/secret.js.map +1 -0
- package/dist/cli/session.d.ts +24 -0
- package/dist/cli/session.js +119 -0
- package/dist/cli/session.js.map +1 -0
- package/dist/cli/skill.d.ts +37 -14
- package/dist/cli/skill.js +36 -30
- package/dist/cli/skill.js.map +1 -1
- package/dist/config.d.ts +23 -2
- package/dist/config.js +50 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/dist/secretStore.d.ts +49 -0
- package/dist/secretStore.js +244 -0
- package/dist/secretStore.js.map +1 -0
- package/dist/sessionCheck.d.ts +96 -0
- package/dist/sessionCheck.js +182 -0
- package/dist/sessionCheck.js.map +1 -0
- package/dist/unrepaired.d.ts +73 -0
- package/dist/unrepaired.js +0 -0
- package/dist/unrepaired.js.map +1 -0
- package/dist/volatile.d.ts +8 -0
- package/dist/volatile.js +24 -7
- package/dist/volatile.js.map +1 -1
- package/dist/volatileBlindSpot.d.ts +67 -0
- package/dist/volatileBlindSpot.js +0 -0
- package/dist/volatileBlindSpot.js.map +1 -0
- package/package.json +1 -1
- package/skills/ia-qa-heal/SKILL.md +29 -3
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { Usage } from './ingest';
|
|
2
|
+
/**
|
|
3
|
+
* "No test file referenced any of the drifted selectors" — the false negative.
|
|
4
|
+
*
|
|
5
|
+
* Field report: that line was printed over a project whose Page Object contains, verbatim,
|
|
6
|
+
* `a[href="/index.php/cart"]` — one of the drifted selectors. The statement was simply
|
|
7
|
+
* untrue, and the reader could only conclude the inventory was broken.
|
|
8
|
+
*
|
|
9
|
+
* The line was measuring the wrong set. `rewrites` holds the rows the tool decided it could
|
|
10
|
+
* **repair**; a row that drifted and could not be repaired is in neither `rewrites` nor the
|
|
11
|
+
* resulting edits, so "nothing matched a file" got reported as "your tests do not use any of
|
|
12
|
+
* this". Two very different sentences: one is about the suite, the other about the tool's
|
|
13
|
+
* own reach.
|
|
14
|
+
*
|
|
15
|
+
* What the reader needed was the third sentence, which nobody was writing: **your tests do
|
|
16
|
+
* reference it, and here is why I cannot repair it.** Saying "no" where the honest answer is
|
|
17
|
+
* "I don't know, for this reason" is the single failure mode that costs the most trust,
|
|
18
|
+
* because it is indistinguishable from a working check.
|
|
19
|
+
*
|
|
20
|
+
* Everything here is pure, and it reads the two files that were already on disk the whole
|
|
21
|
+
* time: the drift rows, and `usage.json`.
|
|
22
|
+
*/
|
|
23
|
+
export interface UnrepairedRow {
|
|
24
|
+
status: string;
|
|
25
|
+
role?: string;
|
|
26
|
+
name?: string;
|
|
27
|
+
selector?: string;
|
|
28
|
+
newName?: string;
|
|
29
|
+
candidateCount?: number;
|
|
30
|
+
nameMasked?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface UnrepairedFinding {
|
|
33
|
+
selector?: string;
|
|
34
|
+
role: string;
|
|
35
|
+
name: string;
|
|
36
|
+
status: string;
|
|
37
|
+
/** Why this one could not be repaired, in a sentence about the element. */
|
|
38
|
+
why: string;
|
|
39
|
+
/** How the suite reaches it. */
|
|
40
|
+
via: Array<'selector' | 'name'>;
|
|
41
|
+
sites: Array<{
|
|
42
|
+
file: string;
|
|
43
|
+
line: number;
|
|
44
|
+
}>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Why a drifted row the suite uses was left alone.
|
|
48
|
+
*
|
|
49
|
+
* Each sentence names the property of the ELEMENT that makes the repair unprovable, not the
|
|
50
|
+
* internal status word — a reader who is told `ambiguous` has learned a vocabulary, and a
|
|
51
|
+
* reader told "two elements now share that role and name, so a repair would be a coin flip"
|
|
52
|
+
* has learned what to go and look at.
|
|
53
|
+
*/
|
|
54
|
+
export declare function reasonFor(row: UnrepairedRow): string;
|
|
55
|
+
/**
|
|
56
|
+
* Rows that drifted, were not repaired, and that the suite demonstrably uses.
|
|
57
|
+
*
|
|
58
|
+
* `repaired` is the set of selector literals the fix is about to rewrite — anything in it is
|
|
59
|
+
* already reported as an edit and must not be listed again as a gap.
|
|
60
|
+
*
|
|
61
|
+
* No inventory ⇒ no findings, and the caller must then say "not measured" rather than
|
|
62
|
+
* anything resembling "no". That distinction is the whole point of this module.
|
|
63
|
+
*/
|
|
64
|
+
export declare function referencedButUnrepaired(rows: UnrepairedRow[], repaired: Set<string>, usage: Usage | null): UnrepairedFinding[];
|
|
65
|
+
/**
|
|
66
|
+
* The block that replaces "no test file referenced any of the drifted selectors".
|
|
67
|
+
*
|
|
68
|
+
* Three states, and they must stay three: repaired nothing *and* your tests use none of it;
|
|
69
|
+
* repaired nothing *but* your tests use these, here is why; and repaired nothing *and*
|
|
70
|
+
* nobody has told me what your tests use. The middle one did not exist, and the third was
|
|
71
|
+
* being printed as the first.
|
|
72
|
+
*/
|
|
73
|
+
export declare function unrepairedLines(findings: UnrepairedFinding[], hasInventory: boolean): string[];
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unrepaired.js","sourceRoot":"","sources":["../src/unrepaired.ts"],"names":[],"mappings":"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"}
|
package/dist/volatile.d.ts
CHANGED
|
@@ -29,8 +29,16 @@ export interface VolatilePattern {
|
|
|
29
29
|
export declare function parseVolatilePatterns(patterns: string[] | undefined): VolatilePattern[];
|
|
30
30
|
/** Shared with `nameMask.ts`, so both declarations speak one pattern dialect. */
|
|
31
31
|
export declare function globToRegex(glob: string): RegExp;
|
|
32
|
+
/** The pattern that excludes this element, or null. Named, because the message needs it. */
|
|
33
|
+
export declare function matchVolatile(el: MappedElement, patterns: VolatilePattern[]): VolatilePattern | null;
|
|
32
34
|
export declare function isVolatile(el: MappedElement, patterns: VolatilePattern[]): boolean;
|
|
35
|
+
/** An element that left the contract, with the pattern that removed it. */
|
|
36
|
+
export interface VolatileExclusion {
|
|
37
|
+
element: MappedElement;
|
|
38
|
+
pattern: string;
|
|
39
|
+
}
|
|
33
40
|
export declare function filterVolatile(elements: MappedElement[], patterns: VolatilePattern[]): {
|
|
34
41
|
kept: MappedElement[];
|
|
35
42
|
excluded: number;
|
|
43
|
+
exclusions: VolatileExclusion[];
|
|
36
44
|
};
|
package/dist/volatile.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseVolatilePatterns = parseVolatilePatterns;
|
|
4
4
|
exports.globToRegex = globToRegex;
|
|
5
|
+
exports.matchVolatile = matchVolatile;
|
|
5
6
|
exports.isVolatile = isVolatile;
|
|
6
7
|
exports.filterVolatile = filterVolatile;
|
|
7
8
|
const FIELDS = ['href', 'selector', 'name', 'role'];
|
|
@@ -22,27 +23,43 @@ function globToRegex(glob) {
|
|
|
22
23
|
const escaped = glob.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
23
24
|
return new RegExp(`^${escaped}$`, 'i');
|
|
24
25
|
}
|
|
25
|
-
|
|
26
|
+
/** The pattern that excludes this element, or null. Named, because the message needs it. */
|
|
27
|
+
function matchVolatile(el, patterns) {
|
|
26
28
|
for (const p of patterns) {
|
|
27
29
|
if (p.field === 'any') {
|
|
28
30
|
if ((el.href !== undefined && p.regex.test(el.href)) ||
|
|
29
31
|
p.regex.test(el.selector) ||
|
|
30
32
|
p.regex.test(el.name)) {
|
|
31
|
-
return
|
|
33
|
+
return p;
|
|
32
34
|
}
|
|
33
35
|
}
|
|
34
36
|
else {
|
|
35
37
|
const value = el[p.field];
|
|
36
38
|
if (value !== undefined && p.regex.test(value))
|
|
37
|
-
return
|
|
39
|
+
return p;
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
|
-
return
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function isVolatile(el, patterns) {
|
|
45
|
+
return matchVolatile(el, patterns) !== null;
|
|
41
46
|
}
|
|
42
47
|
function filterVolatile(elements, patterns) {
|
|
43
48
|
if (patterns.length === 0)
|
|
44
|
-
return { kept: elements, excluded: 0 };
|
|
45
|
-
const kept =
|
|
46
|
-
|
|
49
|
+
return { kept: elements, excluded: 0, exclusions: [] };
|
|
50
|
+
const kept = [];
|
|
51
|
+
const exclusions = [];
|
|
52
|
+
for (const el of elements) {
|
|
53
|
+
const hit = matchVolatile(el, patterns);
|
|
54
|
+
// The excluded elements are kept, not just counted. They are the only evidence that
|
|
55
|
+
// can answer "is anything my tests use being silently dropped?" — see
|
|
56
|
+
// `volatileBlindSpots`, and they exist nowhere else: by construction they never
|
|
57
|
+
// reach a contract.
|
|
58
|
+
if (hit)
|
|
59
|
+
exclusions.push({ element: el, pattern: hit.raw });
|
|
60
|
+
else
|
|
61
|
+
kept.push(el);
|
|
62
|
+
}
|
|
63
|
+
return { kept, excluded: exclusions.length, exclusions };
|
|
47
64
|
}
|
|
48
65
|
//# sourceMappingURL=volatile.js.map
|
package/dist/volatile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"volatile.js","sourceRoot":"","sources":["../src/volatile.ts"],"names":[],"mappings":";;AAiCA,sDAWC;AAGD,kCAGC;
|
|
1
|
+
{"version":3,"file":"volatile.js","sourceRoot":"","sources":["../src/volatile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Usage } from './ingest';
|
|
2
|
+
import type { VolatileExclusion } from './volatile';
|
|
3
|
+
/**
|
|
4
|
+
* The declaration that removes an element, checked against the suite that uses it.
|
|
5
|
+
*
|
|
6
|
+
* `volatile` is the most destructive declaration this tool takes: a matching element never
|
|
7
|
+
* enters a contract at all — not its selector, not its name, not its href, not its
|
|
8
|
+
* coverage. That is correct for a rotating feed item, and it is a **permanent blind spot**
|
|
9
|
+
* for anything else. Nothing ever reports drift on an element that is not in the contract,
|
|
10
|
+
* because from the gate's point of view it does not exist.
|
|
11
|
+
*
|
|
12
|
+
* Until now `map` printed only a count — `N volatile excluded` — and a count cannot
|
|
13
|
+
* distinguish "40 feed cards" from "40 feed cards and the cart button your suite clicks".
|
|
14
|
+
* Field report: an element excluded by a pattern was referenced by a Page Object, and
|
|
15
|
+
* nobody was told for fifteen days.
|
|
16
|
+
*
|
|
17
|
+
* **Its conservative sibling already does this.** `nameMask.ts` refuses a mask that would
|
|
18
|
+
* swallow a label `usage.names` says the tests locate by — the exact same cross-check,
|
|
19
|
+
* between the exact same two files. `volatile` is the one that removes more and checked
|
|
20
|
+
* less.
|
|
21
|
+
*
|
|
22
|
+
* Three restraints, and they are what makes this a warning worth reading:
|
|
23
|
+
*
|
|
24
|
+
* **It warns, it never refuses.** A mask is a comparison-time policy and can be declined
|
|
25
|
+
* without losing anything; `volatile` is applied at capture, and refusing it would either
|
|
26
|
+
* write a contract the user did not ask for or abandon the page. The user declared this on
|
|
27
|
+
* purpose. What they did not choose is being uninformed about it.
|
|
28
|
+
*
|
|
29
|
+
* **No inventory ⇒ no message.** A project that has never run `ingest` has no evidence
|
|
30
|
+
* either way, and "no test uses it" is exactly the false negative this file exists to stop
|
|
31
|
+
* being printed. Silence here means *not measured*, and the caller says so rather than
|
|
32
|
+
* implying the exclusions are safe.
|
|
33
|
+
*
|
|
34
|
+
* **Silent on the good case.** An exclusion nothing references produces no line at all. A
|
|
35
|
+
* warning that fires on the ordinary run is one people learn to scroll past, and then it is
|
|
36
|
+
* not there on the day it matters.
|
|
37
|
+
*/
|
|
38
|
+
export interface VolatileBlindSpot {
|
|
39
|
+
/** What was excluded, as the contract would have addressed it. */
|
|
40
|
+
selector: string;
|
|
41
|
+
name: string;
|
|
42
|
+
role: string;
|
|
43
|
+
/** The `volatile` pattern that removed it — the line the user would edit. */
|
|
44
|
+
pattern: string;
|
|
45
|
+
/** How the suite reaches it: by selector, by accessible name, or both. */
|
|
46
|
+
via: Array<'selector' | 'name'>;
|
|
47
|
+
/** Where, in the user's own files. */
|
|
48
|
+
sites: Array<{
|
|
49
|
+
file: string;
|
|
50
|
+
line: number;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Which excluded elements the suite actually uses.
|
|
55
|
+
*
|
|
56
|
+
* Deduplicated by selector + name: a navbar element excluded on 142 pages is one blind
|
|
57
|
+
* spot and one fix, not 142 findings — the same reasoning `check` applies to dead links.
|
|
58
|
+
*/
|
|
59
|
+
export declare function volatileBlindSpots(exclusions: VolatileExclusion[], usage: Usage | null): VolatileBlindSpot[];
|
|
60
|
+
/**
|
|
61
|
+
* The warning, as one block.
|
|
62
|
+
*
|
|
63
|
+
* Rendered here rather than in the CLI so the message cannot be worded one way by `map` and
|
|
64
|
+
* another by whatever reads this next — the `checkHeadline` rule. Returns null when there
|
|
65
|
+
* is nothing to say, which is the ordinary case.
|
|
66
|
+
*/
|
|
67
|
+
export declare function volatileBlindSpotLines(spots: VolatileBlindSpot[]): string[] | null;
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"volatileBlindSpot.js","sourceRoot":"","sources":["../src/volatileBlindSpot.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ia-qa/self-healing",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.19.1",
|
|
4
4
|
"description": "Your Playwright, Cypress or Selenium tests break when a selector moves — this finds the element again and rewrites the test. Deterministic: no LLM decides whether your build passes. Runs entirely on your machine, with a local MCP server for agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"self-healing",
|
|
@@ -64,6 +64,7 @@ exists only in a state nothing captured (behind a tab, a modal, a mode toggle).
|
|
|
64
64
|
| "is this getting better or worse?" | `history` (`--json`) — the trend no single run can reconstruct |
|
|
65
65
|
| "gate my pipeline on it" | `diff --junit <file>` — every major CI charts the trend natively |
|
|
66
66
|
| `map` says "login wall" / the app is behind SSO | if a storageState already exists (most authenticated Playwright suites write one in `globalSetup`): `--session <file>`, `IAQA_SESSION`, or `"session"` in config.json — no second login. Otherwise tell the human to run `ia-qa-heal login` — **you cannot do it** (§4.12) |
|
|
67
|
+
| "is the session still good?" / before blaming the app for a wall | `session --check`. It tells the three causes **apart**: `expired` (every cookie's date is past) and `wrong-host` (nothing is scoped to `baseUrl`) are decided **from the file, no browser**; `rotated` (the file is fine, the server still answers a login) needs one request. Exit 0 holds · 1 does not · 2 could not tell. `--offline` skips the browser entirely. Run it **before** a long `map`, and when a tour or a map reports a wall — a `wrong-host` is the one no amount of logging in will fix |
|
|
67
68
|
| "let me look at it / decide myself" | tell the human to run `ia-qa-heal ui` — **do not run it yourself** (§4.8) |
|
|
68
69
|
| first-time setup | `ia-qa-heal init --yes --base-url <url> --discover [--tests <paths>] [--bootstrap]` — asks nothing, so it cannot hang. `--discover` fills `pages` from their sitemap (plain fetch, no browser); `--bootstrap` also chains ingest → map → baseline. Writing `.ia-qa/config.json` by hand still works |
|
|
69
70
|
| "288 lost, red everywhere, on an app that looks fine" | read the **systemic delta** block `diff` prints — the same element gone from ~every page is one fact about the *capture*, not N drifts. Usually a consent banner, a tour or a flag: the fix is `prepare` in config.json (§4.16), not a rewrite |
|
|
@@ -125,6 +126,9 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
|
|
|
125
126
|
included, so a real break on that button would never be caught again. And never propose a mask
|
|
126
127
|
over a label the tests locate by name: the tool refuses it out loud, and talking the human past
|
|
127
128
|
that refusal is putting a green gate over a red suite.
|
|
129
|
+
**`map` now names the elements a `volatile` pattern removed that the suite still uses**, with
|
|
130
|
+
`file:line` and the pattern to narrow — relay that warning in full. It fires only on the bad
|
|
131
|
+
case, so if you see it, something the tests click will never be gated again.
|
|
128
132
|
10. **Never read a `map` that exited 1 as "nothing was mapped".** A page that fails is now isolated:
|
|
129
133
|
the rest are still written, its own previous contract is left untouched, and the summary says
|
|
130
134
|
`N of M pages mapped`. Read that line — the pages it did not reach are `stale` in the next diff,
|
|
@@ -139,7 +143,13 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
|
|
|
139
143
|
12. **Never try to log a suite in yourself.** When `map` reports a login wall it prints the whole
|
|
140
144
|
remedy: a filled-in `auth` block when the form is readable, and the fact that a form fill
|
|
141
145
|
cannot pass SSO with MFA, a consent screen or a magic link. Read past the first line before
|
|
142
|
-
concluding anything.
|
|
146
|
+
concluding anything. **Run `session --check` first**: it says *which* of expired / wrong-host /
|
|
147
|
+
rotated it is, and two of those need no browser. `wrong-host` means the session and the
|
|
148
|
+
`baseUrl` are about different applications — telling the human to log in again there wastes
|
|
149
|
+
their time and produces the identical file. Note that with a session in use, `map` now stops
|
|
150
|
+
at the first wall instead of walking every remaining page into it: `N of M pages mapped` with
|
|
151
|
+
the rest **not attempted** (they keep their previous contracts and read `stale`), so do not
|
|
152
|
+
report those as pages that failed. For everything a form cannot answer there is exactly one step, and it
|
|
143
153
|
belongs to the human: `ia-qa-heal login` opens a visible browser, they log in once, and the
|
|
144
154
|
session is reused by `map`. It refuses without a TTY and under CI — that refusal is aimed at
|
|
145
155
|
you. Never ask for their password, never put a credential in `config.json` (it holds `secrets`
|
|
@@ -197,11 +207,19 @@ clicking the wrong thing), or a name-drift finding that is not attributable.
|
|
|
197
207
|
`run`'s live coverage and `map`'s "N of M pages": a surface left unmeasured is never a pass.
|
|
198
208
|
The remedy is `--deep-budget <n>` or a narrower `--depth 1`, never ignoring the line.
|
|
199
209
|
|
|
200
|
-
19. **Never
|
|
210
|
+
19. **Never read "nothing could be rewritten" as "their tests do not use it".** `fix` prints
|
|
211
|
+
three different sentences and they mean three different things: *nothing to repair and your
|
|
212
|
+
tests reference none of it*; *nothing to repair **but** your tests DO reference these — here
|
|
213
|
+
is each one with `file:line` and why it could not be repaired*; and *not measured, because no
|
|
214
|
+
`usage.json` exists*. The middle one is the interesting case and the last one is not a "no".
|
|
215
|
+
Relay the reason per element — "two elements now answer to that role and name, so repairing
|
|
216
|
+
would be a coin flip" is what the human acts on, not the status word.
|
|
217
|
+
|
|
218
|
+
20. **Never present `--deep` as free.** It clicks its way through the page's states, so it costs
|
|
201
219
|
~1 min per page where a plain `map` costs seconds. Recommend it for building or refreshing a
|
|
202
220
|
**baseline**, not for a per-commit gate, and say so when you propose it.
|
|
203
221
|
|
|
204
|
-
|
|
222
|
+
21. **Never raise `--depth` to "get better coverage".** The default is 1 because it *finishes*.
|
|
205
223
|
Depth 2 was measured at +30% elements for 3× the time and **never completed at any budget** —
|
|
206
224
|
80, 150 and 250 clicks all returned the same elements, so the extra clicks re-walk states that
|
|
207
225
|
hold nothing new. If a user asks for deeper, tell them what it costs and that the contract
|
|
@@ -227,6 +245,14 @@ Nothing, except: requests to the user's **own** `baseUrl` (`check`'s link half,
|
|
|
227
245
|
— GET only, same origin, never a `/logout` or `/delete` URL), and `ia-qa-heal-ai` calling the
|
|
228
246
|
user's own LLM if they opted in. Say that plainly rather than implying more or less.
|
|
229
247
|
|
|
248
|
+
**Where the key lives.** `ia-qa-heal-ai` needs a provider key, and it is never a file value.
|
|
249
|
+
Prefer the OS store — hand the human `ia-qa-heal secret set <NAME>` to run themselves (it asks
|
|
250
|
+
for the value on a terminal and refuses a pipe or an argument), then reference it as
|
|
251
|
+
`{ "source": "keychain", "key": "<NAME>" }`. You can confirm it resolves with
|
|
252
|
+
`secret check <NAME>`; **there is no verb that prints a stored value and you must not look for
|
|
253
|
+
one**. In CI there is no store and no person: keep `{ "source": "env" }` and let the runner
|
|
254
|
+
hold the secret. Never ask the human to paste a key to you; if they do, tell them to rotate it.
|
|
255
|
+
|
|
230
256
|
`ui` does not change that: it binds `127.0.0.1` only, is gated by a per-process token, and serves a
|
|
231
257
|
page with no external asset. If asked whether it "hosts" anything, the answer is no — it is a local
|
|
232
258
|
process that dies with the browser tab.
|