@lsctech/polaris 0.3.2 → 0.3.4
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/dist/governance/review-packet.js +7 -6
- package/dist/skill-packet/generator.js +40 -1
- package/dist/smartdocs-engine/agentic-review.js +49 -0
- package/dist/smartdocs-engine/index.js +16 -4
- package/dist/smartdocs-engine/review.js +4 -3
- package/dist/workspace/workspace/.polaris/skills/ROUTING.md +2 -0
- package/dist/workspace/workspace/.polaris/skills/docs-review/SKILL.md +58 -0
- package/dist/workspace/workspace/.polaris/skills/docs-review/chain.md +95 -0
- package/package.json +1 -1
|
@@ -52,14 +52,15 @@ function groupAndSort(packets) {
|
|
|
52
52
|
* Write _review-queue.json (canonical) and _review-queue.md (display-only) to outputDir.
|
|
53
53
|
* Markdown is regenerated from JSON — never parse markdown to recover decisions.
|
|
54
54
|
*/
|
|
55
|
-
function writeReviewQueue(packets, runId, outputDir) {
|
|
55
|
+
function writeReviewQueue(packets, runId, outputDir, filename = "_review-queue.json") {
|
|
56
56
|
(0, node_fs_1.mkdirSync)(outputDir, { recursive: true });
|
|
57
57
|
const queueFile = {
|
|
58
58
|
generated_at: new Date().toISOString(),
|
|
59
59
|
run_id: runId,
|
|
60
60
|
packets,
|
|
61
61
|
};
|
|
62
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir,
|
|
62
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, filename), JSON.stringify(queueFile, null, 2) + "\n", "utf-8");
|
|
63
|
+
const mdFilename = filename.replace(/\.json$/, ".md");
|
|
63
64
|
const sorted = groupAndSort(packets);
|
|
64
65
|
const sections = sorted.map(renderPacketMarkdown).join("\n\n---\n\n");
|
|
65
66
|
const md = [
|
|
@@ -69,21 +70,21 @@ function writeReviewQueue(packets, runId, outputDir) {
|
|
|
69
70
|
`**Generated:** ${queueFile.generated_at}`,
|
|
70
71
|
`**Pending review:** ${packets.length} document(s)`,
|
|
71
72
|
``,
|
|
72
|
-
`> Markdown is display-only. Edit \`
|
|
73
|
+
`> Markdown is display-only. Edit \`${filename}\` to set \`reviewDecision\` fields.`,
|
|
73
74
|
`> Rerun \`polaris docs ingest\` to apply decisions.`,
|
|
74
75
|
``,
|
|
75
76
|
`---`,
|
|
76
77
|
``,
|
|
77
78
|
sections,
|
|
78
79
|
].join("\n");
|
|
79
|
-
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir,
|
|
80
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(outputDir, mdFilename), md, "utf-8");
|
|
80
81
|
}
|
|
81
82
|
/**
|
|
82
83
|
* Read review queue from JSON. Returns empty array if no queue file exists.
|
|
83
84
|
* Never reads markdown.
|
|
84
85
|
*/
|
|
85
|
-
function readReviewQueue(outputDir) {
|
|
86
|
-
const jsonPath = (0, node_path_1.join)(outputDir,
|
|
86
|
+
function readReviewQueue(outputDir, filename = "_review-queue.json") {
|
|
87
|
+
const jsonPath = (0, node_path_1.join)(outputDir, filename);
|
|
87
88
|
if (!(0, node_fs_1.existsSync)(jsonPath))
|
|
88
89
|
return [];
|
|
89
90
|
try {
|
|
@@ -9,6 +9,7 @@ exports.SKILL_ROLE_MAP = {
|
|
|
9
9
|
ingest: "Librarian",
|
|
10
10
|
promote: "Librarian",
|
|
11
11
|
triage: "Librarian",
|
|
12
|
+
review: "Librarian",
|
|
12
13
|
};
|
|
13
14
|
const ROLE_SUMMARIES = {
|
|
14
15
|
Analyst: "The Analyst gathers evidence, assesses feasibility, and produces implementation-ready plans. The Analyst shapes work but never executes it.",
|
|
@@ -221,6 +222,41 @@ function buildTriagePacket() {
|
|
|
221
222
|
],
|
|
222
223
|
};
|
|
223
224
|
}
|
|
225
|
+
function buildReviewPacket() {
|
|
226
|
+
return {
|
|
227
|
+
authority_boundaries: [
|
|
228
|
+
"Read smartdocs/raw/_triage-queue.json to load pending review decisions",
|
|
229
|
+
"Read candidate documents from smartdocs/doctrine/candidate/ to inform decisions",
|
|
230
|
+
"Evaluate each flagged packet: read the doc, consider the flag type and stale symbols, decide approve/reject/defer",
|
|
231
|
+
"Write decisions back to the triage queue by calling polaris docs review --write-decision <sourcePath> <decision>",
|
|
232
|
+
"Call polaris docs ingest to apply approved/rejected decisions after review is complete",
|
|
233
|
+
"Emit telemetry events",
|
|
234
|
+
],
|
|
235
|
+
prohibited_actions: [
|
|
236
|
+
"Move, promote, or delete documents directly — decisions must go through polaris docs ingest",
|
|
237
|
+
"Auto-approve every packet without reading the document content",
|
|
238
|
+
"Mutate source files (src/, tests, config)",
|
|
239
|
+
"Call polaris loop continue or polaris finalize",
|
|
240
|
+
"Suppress or skip flagged packets without recording a decision",
|
|
241
|
+
],
|
|
242
|
+
allowed_outputs: [
|
|
243
|
+
"Review decisions written to smartdocs/raw/_triage-queue.json",
|
|
244
|
+
"Promoted documents (via polaris docs ingest for approved packets)",
|
|
245
|
+
"Deprecated documents (via polaris docs ingest for rejected packets)",
|
|
246
|
+
"Telemetry events",
|
|
247
|
+
],
|
|
248
|
+
deliverables: [
|
|
249
|
+
"All pending packets in _triage-queue.json reviewed with approve/reject/defer decision",
|
|
250
|
+
"Approved and rejected packets processed by polaris docs ingest",
|
|
251
|
+
"Summary of decisions reported to user",
|
|
252
|
+
],
|
|
253
|
+
stop_conditions: [
|
|
254
|
+
"All packets reviewed",
|
|
255
|
+
"Ambiguous packet that requires explicit user input",
|
|
256
|
+
"Ingest error on apply — report and wait for instruction",
|
|
257
|
+
],
|
|
258
|
+
};
|
|
259
|
+
}
|
|
224
260
|
function generateSkillPacket(skillName, config) {
|
|
225
261
|
const active_role = exports.SKILL_ROLE_MAP[skillName];
|
|
226
262
|
const role_summary = ROLE_SUMMARIES[active_role];
|
|
@@ -248,6 +284,9 @@ function generateSkillPacket(skillName, config) {
|
|
|
248
284
|
case "triage":
|
|
249
285
|
body = buildTriagePacket();
|
|
250
286
|
break;
|
|
287
|
+
case "review":
|
|
288
|
+
body = buildReviewPacket();
|
|
289
|
+
break;
|
|
251
290
|
}
|
|
252
291
|
return {
|
|
253
292
|
packet_id,
|
|
@@ -259,4 +298,4 @@ function generateSkillPacket(skillName, config) {
|
|
|
259
298
|
generated_at,
|
|
260
299
|
};
|
|
261
300
|
}
|
|
262
|
-
exports.SUPPORTED_SKILLS = ["analyze", "run", "ingest", "promote", "triage"];
|
|
301
|
+
exports.SUPPORTED_SKILLS = ["analyze", "run", "ingest", "promote", "triage", "review"];
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.agenticDecideKey = agenticDecideKey;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
async function agenticDecideKey(packet) {
|
|
6
|
+
let docContent = "";
|
|
7
|
+
try {
|
|
8
|
+
if ((0, node_fs_1.existsSync)(packet.sourcePath)) {
|
|
9
|
+
docContent = (0, node_fs_1.readFileSync)(packet.sourcePath, "utf-8").slice(0, 3000);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
catch { /* ignore */ }
|
|
13
|
+
// @ts-ignore: @anthropic-ai/sdk may not be installed as a dependency
|
|
14
|
+
const mod = await import("@anthropic-ai/sdk");
|
|
15
|
+
const Anthropic = mod.default ?? mod.Anthropic;
|
|
16
|
+
const client = new Anthropic();
|
|
17
|
+
const staleSymbols = packet.staleSymbols;
|
|
18
|
+
const triageFlag = packet.triageFlag;
|
|
19
|
+
const prompt = `You are reviewing a candidate documentation file flagged during Polaris docs triage.
|
|
20
|
+
|
|
21
|
+
Flag type: ${String(triageFlag ?? "unknown")}
|
|
22
|
+
Authority risk: ${packet.authorityRisk}
|
|
23
|
+
Recommendation from triage: ${packet.recommendation}
|
|
24
|
+
Reasoning: ${packet.reasoning.join("; ")}
|
|
25
|
+
${Array.isArray(staleSymbols) ? `Stale symbols (not found in codebase graph): ${staleSymbols.join(", ")}` : ""}
|
|
26
|
+
|
|
27
|
+
Document content:
|
|
28
|
+
---
|
|
29
|
+
${docContent}
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
Decide whether this document should be:
|
|
33
|
+
- approved: doc is valid doctrine and should be promoted to active
|
|
34
|
+
- rejected: doc is outdated, incorrect, or superseded and should be deprecated
|
|
35
|
+
- deferred: uncertain — needs human review
|
|
36
|
+
|
|
37
|
+
Respond with exactly one word: approve, reject, or defer.`;
|
|
38
|
+
const response = await client.messages.create({
|
|
39
|
+
model: "claude-haiku-4-5-20251001",
|
|
40
|
+
max_tokens: 10,
|
|
41
|
+
messages: [{ role: "user", content: prompt }],
|
|
42
|
+
});
|
|
43
|
+
const text = (response.content[0].text ?? "defer").trim().toLowerCase();
|
|
44
|
+
if (text.startsWith("approve"))
|
|
45
|
+
return "a";
|
|
46
|
+
if (text.startsWith("reject"))
|
|
47
|
+
return "r";
|
|
48
|
+
return "d";
|
|
49
|
+
}
|
|
@@ -142,12 +142,24 @@ function createDocsCommand(options = {}) {
|
|
|
142
142
|
.description("Interactively review pending governance decisions in the review queue")
|
|
143
143
|
.option("--queue <path>", "path to _review-queue.json (default: smartdocs/raw/_review-queue.json)")
|
|
144
144
|
.option("-r, --repo-root <path>", "Repository root", defaultRepoRoot)
|
|
145
|
+
.option("--agentic", "use LLM agent to make review decisions automatically")
|
|
146
|
+
.option("--triage", "review the triage queue (_triage-queue.json) instead of the review queue")
|
|
145
147
|
.action(async (opts) => {
|
|
148
|
+
const repoRoot = opts.repoRoot;
|
|
149
|
+
const queueFilename = opts.triage ? "_triage-queue.json" : "_review-queue.json";
|
|
150
|
+
const queueDir = opts.queue
|
|
151
|
+
? (0, node_path_1.resolve)(opts.repoRoot, opts.queue)
|
|
152
|
+
.replace(/_review-queue\.json$/, "")
|
|
153
|
+
.replace(/_triage-queue\.json$/, "")
|
|
154
|
+
.replace(/\/$/, "")
|
|
155
|
+
: (0, node_path_1.resolve)(repoRoot, "smartdocs", "raw");
|
|
156
|
+
let readKey;
|
|
157
|
+
if (opts.agentic) {
|
|
158
|
+
const { agenticDecideKey } = await import("./agentic-review.js");
|
|
159
|
+
readKey = agenticDecideKey;
|
|
160
|
+
}
|
|
146
161
|
try {
|
|
147
|
-
|
|
148
|
-
? (0, node_path_1.resolve)(opts.repoRoot, opts.queue).replace(/_review-queue\.json$/, "").replace(/\/$/, "")
|
|
149
|
-
: (0, node_path_1.resolve)(opts.repoRoot, "smartdocs", "raw");
|
|
150
|
-
await (0, review_js_1.runReviewSession)({ repoRoot: opts.repoRoot, queueDir });
|
|
162
|
+
await (0, review_js_1.runReviewSession)({ repoRoot, queueDir, queueFilename, readKey });
|
|
151
163
|
}
|
|
152
164
|
catch (err) {
|
|
153
165
|
console.error(`polaris docs review: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -84,7 +84,8 @@ function readSingleKey() {
|
|
|
84
84
|
async function runReviewSession(options) {
|
|
85
85
|
const { repoRoot, readKey, getReviewedBy = defaultGetReviewedBy, output = (msg) => process.stdout.write(msg + "\n"), } = options;
|
|
86
86
|
const queueDir = options.queueDir ?? (0, node_path_1.resolve)(repoRoot, "smartdocs", "raw");
|
|
87
|
-
const
|
|
87
|
+
const queueFilename = options.queueFilename;
|
|
88
|
+
const packets = (0, index_js_1.readReviewQueue)(queueDir, queueFilename);
|
|
88
89
|
if (packets.length === 0) {
|
|
89
90
|
output("No review queue found. Run polaris docs ingest first.");
|
|
90
91
|
return;
|
|
@@ -98,7 +99,7 @@ async function runReviewSession(options) {
|
|
|
98
99
|
for (let i = 0; i < undecided.length; i++) {
|
|
99
100
|
const packet = undecided[i];
|
|
100
101
|
output(formatPacketCard(packet, i + 1, undecided.length));
|
|
101
|
-
const key = readKey ? await readKey() : await readSingleKey();
|
|
102
|
+
const key = readKey ? await readKey(packet) : await readSingleKey();
|
|
102
103
|
if (key === "q") {
|
|
103
104
|
const remaining = undecided.length - decided;
|
|
104
105
|
output(`\nSession ended. ${decided} decision(s) saved, ${remaining} packet(s) still pending.`);
|
|
@@ -126,7 +127,7 @@ async function runReviewSession(options) {
|
|
|
126
127
|
reviewedBy: getReviewedBy(),
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
|
-
(0, index_js_1.writeReviewQueue)(packets, "review-session", queueDir);
|
|
130
|
+
(0, index_js_1.writeReviewQueue)(packets, "review-session", queueDir, queueFilename);
|
|
130
131
|
decided++;
|
|
131
132
|
}
|
|
132
133
|
const approved = packets.filter((p) => p.reviewDecision === "approve").length;
|
|
@@ -35,6 +35,8 @@ message whose primary instruction is to invoke a named Polaris skill.
|
|
|
35
35
|
| `run docs-ingest` | docs-ingest | `.polaris/skills/docs-ingest/` |
|
|
36
36
|
| `docs-triage` | docs-triage | `.polaris/skills/docs-triage/` |
|
|
37
37
|
| `run docs-triage` | docs-triage | `.polaris/skills/docs-triage/` |
|
|
38
|
+
| `docs-review` | docs-review | `.polaris/skills/docs-review/` |
|
|
39
|
+
| `run docs-review` | docs-review | `.polaris/skills/docs-review/` |
|
|
38
40
|
| `polaris-reconcile <CLUSTER-ID>` | polaris-reconcile | `.polaris/skills/polaris-reconcile/` |
|
|
39
41
|
| `run polaris-reconcile on [issue] <CLUSTER-ID>` | polaris-reconcile | `.polaris/skills/polaris-reconcile/` |
|
|
40
42
|
| `polaris-catalog <CLUSTER-ID>` | polaris-catalog | `.polaris/skills/polaris-catalog/` |
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-review
|
|
3
|
+
description: Agentically review the triage queue — read each flagged candidate doc, evaluate the flag, and record approve/reject/defer decisions. Applies approved and rejected decisions via polaris docs ingest.
|
|
4
|
+
role: librarian
|
|
5
|
+
role_file: .polaris/roles/librarian.md
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Polaris Skill Bootloader
|
|
9
|
+
|
|
10
|
+
**Before proceeding, you must obtain a skill packet from the Polaris runtime.**
|
|
11
|
+
|
|
12
|
+
Run the following command:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
npm run polaris -- skill packet review
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- Do not begin work until a packet is returned.
|
|
19
|
+
- Treat the packet as your authoritative instruction source.
|
|
20
|
+
- The packet defines your active role, authority boundaries, prohibited actions, deliverables, and stop conditions.
|
|
21
|
+
- If no packet is produced, stop and report: **Polaris could not authorize this run.**
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# docs-review
|
|
26
|
+
|
|
27
|
+
Use this skill to agentically walk through the triage queue and make approve/reject/defer decisions on each flagged candidate document. You are the reviewer — read each doc, evaluate the flag, and decide.
|
|
28
|
+
|
|
29
|
+
## When to use
|
|
30
|
+
|
|
31
|
+
- "Run docs review"
|
|
32
|
+
- "Review the triage queue"
|
|
33
|
+
- "Process the flagged candidates"
|
|
34
|
+
- "docs-review"
|
|
35
|
+
|
|
36
|
+
## How to execute
|
|
37
|
+
|
|
38
|
+
1. Read `chain.md` — step order, traversal rules, output contract.
|
|
39
|
+
2. Execute steps in the order `chain.md` defines. Do not skip steps.
|
|
40
|
+
3. You make all decisions by reading the flagged doc content and the flag metadata.
|
|
41
|
+
|
|
42
|
+
## Hard rules — what docs-review may do
|
|
43
|
+
|
|
44
|
+
- Read `smartdocs/raw/_triage-queue.json` to load pending items
|
|
45
|
+
- Read candidate documents from `smartdocs/doctrine/candidate/`
|
|
46
|
+
- Record decisions (approve/reject/defer) back to `_triage-queue.json`
|
|
47
|
+
- Call `polaris docs ingest` to apply approved/rejected decisions
|
|
48
|
+
- Emit telemetry events
|
|
49
|
+
|
|
50
|
+
## Hard rules — what docs-review must NOT do
|
|
51
|
+
|
|
52
|
+
- Move, rename, or delete documents directly — ingest handles that
|
|
53
|
+
- Auto-approve every packet without reading the document
|
|
54
|
+
- Mutate source files (`src/`, tests, config)
|
|
55
|
+
- Call `polaris loop continue` or `polaris finalize`
|
|
56
|
+
- Skip packets without recording a decision
|
|
57
|
+
|
|
58
|
+
**Docs-review reads, evaluates, and decides. Ingest executes.**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: docs-review-chain
|
|
3
|
+
description: Route map for docs-review — step order, stop conditions, output contract, and decision protocol.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# docs-review chain
|
|
7
|
+
|
|
8
|
+
## Authority
|
|
9
|
+
|
|
10
|
+
**Polaris runtime state is authoritative. Chat reasoning is not authoritative.**
|
|
11
|
+
|
|
12
|
+
Query `smartdocs/raw/_triage-queue.json` before acting. Do not infer queue state from conversation context.
|
|
13
|
+
|
|
14
|
+
## Decision protocol
|
|
15
|
+
|
|
16
|
+
For each flagged packet you evaluate:
|
|
17
|
+
|
|
18
|
+
1. Read the source document at `sourcePath`
|
|
19
|
+
2. Consider the flag metadata: `triageFlag`, `staleSymbols`, `authorityRisk`, `reasoning`
|
|
20
|
+
3. Apply this decision rubric:
|
|
21
|
+
- **approve**: doc content is still valid doctrine; stale symbols are incidental (generic English words, external brand names, or aspirational references that don't affect correctness)
|
|
22
|
+
- **reject**: doc is clearly outdated, contradicted by current code, or describes systems that no longer exist
|
|
23
|
+
- **defer**: cannot determine from doc content alone — needs human review
|
|
24
|
+
|
|
25
|
+
4. Record your decision by editing `_triage-queue.json` directly: set `reviewDecision` to `"approve"`, `"reject"`, or `"defer"`, and set `reviewedAt` to the current ISO timestamp and `reviewedBy` to `"docs-review-agent"`.
|
|
26
|
+
|
|
27
|
+
## Output contract
|
|
28
|
+
|
|
29
|
+
All decision output written to `smartdocs/raw/`:
|
|
30
|
+
|
|
31
|
+
| File | Purpose |
|
|
32
|
+
|------|---------|
|
|
33
|
+
| `_triage-queue.json` | Updated with `reviewDecision` fields — source of truth for ingest |
|
|
34
|
+
|
|
35
|
+
## Step traversal order
|
|
36
|
+
|
|
37
|
+
```text
|
|
38
|
+
01-load-queue ← read _triage-queue.json; count pending items; emit run-start telemetry
|
|
39
|
+
02-review-packets ← for each pending packet: read doc → evaluate → record decision
|
|
40
|
+
03-apply-decisions ← run polaris docs ingest to apply approved/rejected packets
|
|
41
|
+
04-hand-off ← report decision summary (approved / rejected / deferred); emit triage-review-complete telemetry
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Stop conditions
|
|
45
|
+
|
|
46
|
+
**Step 01:**
|
|
47
|
+
- `_triage-queue.json` not found → halt: "Run docs-triage first to generate the triage queue"
|
|
48
|
+
- Zero pending items → report "Nothing to review — all decisions already recorded" and stop
|
|
49
|
+
|
|
50
|
+
**Step 02:**
|
|
51
|
+
- If a document at `sourcePath` cannot be read → record `defer` and log the path
|
|
52
|
+
- Process all packets before moving to step 03 — do not apply decisions mid-loop
|
|
53
|
+
|
|
54
|
+
**Step 03:**
|
|
55
|
+
- If no packets were approved or rejected → skip ingest, report counts only
|
|
56
|
+
- Ingest error → report the error; do not retry automatically
|
|
57
|
+
|
|
58
|
+
## Run ID format
|
|
59
|
+
|
|
60
|
+
Format: `docs-review-<date>-<seq>`
|
|
61
|
+
- `<date>`: `YYYY-MM-DD`
|
|
62
|
+
- `<seq>`: zero-padded sequential number per day (`001`, `002`, …)
|
|
63
|
+
|
|
64
|
+
Example: `docs-review-2026-06-14-001`
|
|
65
|
+
|
|
66
|
+
## Telemetry
|
|
67
|
+
|
|
68
|
+
Telemetry file: `.taskchain_artifacts/docs-review/runs/<run-id>/telemetry.jsonl` (append-only).
|
|
69
|
+
|
|
70
|
+
| Event | Trigger |
|
|
71
|
+
|-------|---------|
|
|
72
|
+
| `run-start` | Begin step 01 |
|
|
73
|
+
| `step-complete` | End of every step |
|
|
74
|
+
| `review-complete` | All packets evaluated |
|
|
75
|
+
| `apply-complete` | Ingest finished |
|
|
76
|
+
|
|
77
|
+
Required fields on every event: `event`, `run_id`, `timestamp`.
|
|
78
|
+
|
|
79
|
+
## Execution reporting
|
|
80
|
+
|
|
81
|
+
After each completed step, emit a checkpoint:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
**[step-name]** done | blocked | needs-input
|
|
85
|
+
Changed: <outputs written> or none
|
|
86
|
+
Validated: <checks passed> or none
|
|
87
|
+
Blockers: none | <explicit blocker>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Never compressed
|
|
91
|
+
|
|
92
|
+
Always write in full:
|
|
93
|
+
- Decision counts (approved / rejected / deferred) after step 02
|
|
94
|
+
- Any packets that could not be read (defaulted to defer)
|
|
95
|
+
- Ingest results after step 03
|