disensor 0.6.2__tar.gz → 0.6.4__tar.gz
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.
- disensor-0.6.4/PKG-INFO +382 -0
- disensor-0.6.4/README.md +368 -0
- {disensor-0.6.2 → disensor-0.6.4}/pyproject.toml +2 -2
- disensor-0.6.4/src/disensor/GUIDE.es.md +201 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/GUIDE.md +41 -17
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/__init__.py +1 -1
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/cli.py +2 -0
- disensor-0.6.4/src/disensor/guide.py +60 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/init.py +10 -9
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/residue.schema.json +1 -1
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/rules.py +4 -1
- disensor-0.6.4/src/disensor.egg-info/PKG-INFO +382 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor.egg-info/SOURCES.txt +2 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_docs.py +76 -1
- disensor-0.6.4/tests/test_docs_sync.py +191 -0
- disensor-0.6.4/tests/test_guide.py +171 -0
- disensor-0.6.2/PKG-INFO +0 -239
- disensor-0.6.2/README.md +0 -225
- disensor-0.6.2/src/disensor/guide.py +0 -40
- disensor-0.6.2/src/disensor.egg-info/PKG-INFO +0 -239
- disensor-0.6.2/tests/test_guide.py +0 -36
- {disensor-0.6.2 → disensor-0.6.4}/LICENSE +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/setup.cfg +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/__main__.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/brief.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/gate.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/gitctx.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/prompts/_common.md +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/prompts/architecture.md +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/prompts/diff.md +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/prompts/plan.md +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/render.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/scope.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/template.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor/vectors.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor.egg-info/dependency_links.txt +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor.egg-info/entry_points.txt +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor.egg-info/requires.txt +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/src/disensor.egg-info/top_level.txt +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_brief.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_cli_errors.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_gate.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_init.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_rules.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_scope.py +0 -0
- {disensor-0.6.2 → disensor-0.6.4}/tests/test_vectors.py +0 -0
disensor-0.6.4/PKG-INFO
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: disensor
|
|
3
|
+
Version: 0.6.4
|
|
4
|
+
Summary: Adversarial plan & code review with a declared residue. Emits, validates and CI-enforces residue declarations (residue/v0.3 schema).
|
|
5
|
+
Author-email: Nicolas Rocchia <nicolasrocchia@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: jsonschema>=4.18
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# disensor
|
|
16
|
+
|
|
17
|
+
Adversarial plan & code review with a declared residue.
|
|
18
|
+
|
|
19
|
+
*Este documento también está [en español](https://github.com/NicolasRocchia/disensor/blob/main/README.es.md).*
|
|
20
|
+
|
|
21
|
+
Residue declaration of adversarial review, with validation and a CI gate.
|
|
22
|
+
Reference implementation of the artifact defined by the **controlled
|
|
23
|
+
disagreement** method: one model generates, a model from another family attacks,
|
|
24
|
+
the generator verifies every finding, and the cycle ends when each finding has
|
|
25
|
+
been resolved, refuted with evidence, or escalated to a human.
|
|
26
|
+
|
|
27
|
+
The artifact this repo defines and enforces records how each review event ended:
|
|
28
|
+
the findings with their terminal state, and the **residue**: what the cycle
|
|
29
|
+
could not close by itself and rests on someone's judgement. The declaration
|
|
30
|
+
lists residue, not coverage: it aims the human reviewer's scrutiny instead of
|
|
31
|
+
reading as a seal of quality.
|
|
32
|
+
|
|
33
|
+
Method paper: Rocchia, N. (2026), *Desacuerdo controlado: revisión adversarial
|
|
34
|
+
automatizada con un segundo asistente de código en el desarrollo de software*,
|
|
35
|
+
DOI [10.5281/zenodo.21633495](https://doi.org/10.5281/zenodo.21633495). The
|
|
36
|
+
paper is in Spanish; the glossary at the end maps its terminology to the schema.
|
|
37
|
+
|
|
38
|
+
## What is here
|
|
39
|
+
|
|
40
|
+
- `spec/residue.schema.json`: the artifact schema (JSON Schema 2020-12), version residue/v0.3.
|
|
41
|
+
- `spec/examples/`: three example artifacts, including a real anonymised event and the minimized profile with no free text.
|
|
42
|
+
- `src/disensor/`: Python package with the validator (rules R0 to R10), the CI gate (checks G1 to G9), the PR comment rendering, artifact and repository scaffolding (`init`), and the packaged filling guide (`GUIDE.md`).
|
|
43
|
+
- `action.yml`: composite GitHub Action, ready to use.
|
|
44
|
+
- `docs/integracion-claude-code.md` (Spanish only): how the real flow (Claude Code plus a reviewer from another family) emits the artifact at the close of each event.
|
|
45
|
+
- `docs/antecedentes.md` (Spanish only): where the method sits relative to the literature (residual doubt and defeaters, design rationale and its capture bottleneck, multi-agent adversarial review, governance runtimes, supply chain provenance), with the verification status of each reference.
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
The package is installed once (globally); each repository is initialised once:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pip install disensor # or pipx install disensor, recommended for CLIs
|
|
53
|
+
|
|
54
|
+
disensor init # at the repo root: config, CLAUDE.md, filling skill and CI workflow
|
|
55
|
+
|
|
56
|
+
disensor prompt --gate diff # the adversarial brief, to hand to a reviewer from another family
|
|
57
|
+
disensor new --gate diff --level B # template prefilled in .residue/
|
|
58
|
+
disensor validate .residue/<id>.json # schema + rules R0 to R10
|
|
59
|
+
disensor gate --no-comment # what CI will run, locally
|
|
60
|
+
|
|
61
|
+
disensor guide # the filling guide, for any agent or human
|
|
62
|
+
disensor guide --lang es # the same guide in Spanish
|
|
63
|
+
disensor prompt --gate diff --hash # the sha256: of the packaged brief, which is what prompt_hash wants
|
|
64
|
+
disensor hash consigna.md # or the hash of yours, if you wrote it
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The brief ships inside the package, so its hash is reproducible: anyone can
|
|
68
|
+
recompute it from the same version and see what the reviewer was actually asked.
|
|
69
|
+
If you edit it, the hash changes and the artifact declares that a different
|
|
70
|
+
brief was used, which is exactly what the field is for.
|
|
71
|
+
|
|
72
|
+
## Trying it without touching your CI
|
|
73
|
+
|
|
74
|
+
There are two modes and it pays not to mix them. To **try it**, you need no
|
|
75
|
+
workflow, no required checks and no organisation permissions: the gate runs the
|
|
76
|
+
same on your machine and says exactly what it would say in CI.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
disensor init --no-workflow # config, CLAUDE.md and skill; without touching .github/
|
|
80
|
+
disensor prompt --gate diff # the brief, to the reviewer from another family
|
|
81
|
+
disensor new --gate diff --level B # and you fill the declaration with what happened
|
|
82
|
+
disensor validate .residue/<id>.json
|
|
83
|
+
disensor gate --no-comment --base <base-sha> --head HEAD
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Only when you want it to **enforce** do you run the full `disensor init` (which
|
|
87
|
+
writes the workflow) and apply the deployment requirements below. Before that it
|
|
88
|
+
is a tool that tells you how you would do; after that it is a control that
|
|
89
|
+
blocks.
|
|
90
|
+
|
|
91
|
+
The Spanish v0.1 subcommands and flags (`nuevo`, `validar`, `--compuerta`,
|
|
92
|
+
`--nivel`, `--directorio`, `--sin-comentario`) still work as aliases.
|
|
93
|
+
|
|
94
|
+
`disensor init` writes, idempotently, the `disensor.config.json` (the level
|
|
95
|
+
travels with the code, in a versioned file), the event-close section in
|
|
96
|
+
`CLAUDE.md`, the Claude Code skill with the full filling guide
|
|
97
|
+
(`.claude/skills/disensor/SKILL.md`, loaded on demand at the close of each
|
|
98
|
+
round) and the gate workflow; whatever already exists is respected and reported.
|
|
99
|
+
The principle is that after `pip install disensor` and `disensor init` the user
|
|
100
|
+
touches nothing by hand: Claude knows when (CLAUDE.md) and how (the skill), any
|
|
101
|
+
other agent gets the same with `disensor guide`, and CI enforces the result.
|
|
102
|
+
Resulting config:
|
|
103
|
+
|
|
104
|
+
```json
|
|
105
|
+
{
|
|
106
|
+
"criticality_level": "B",
|
|
107
|
+
"level_A_enabled": false
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
And the workflow (see `docs/ejemplo-workflow.yml`):
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
on: pull_request
|
|
115
|
+
permissions:
|
|
116
|
+
contents: read
|
|
117
|
+
pull-requests: write
|
|
118
|
+
jobs:
|
|
119
|
+
gate:
|
|
120
|
+
runs-on: ubuntu-latest
|
|
121
|
+
steps:
|
|
122
|
+
- uses: actions/checkout@v4
|
|
123
|
+
with:
|
|
124
|
+
fetch-depth: 0
|
|
125
|
+
- uses: NicolasRocchia/disensor@v0.6.4
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The gate validates the declarations **the PR adds**, applies the policy and
|
|
129
|
+
posts the result as a comment (updated in place on every push). Everything it
|
|
130
|
+
decides comes from git objects in the `merge-base..head` range, never from the
|
|
131
|
+
working tree: on a `pull_request` event the checkout leaves the synthetic merge
|
|
132
|
+
commit while `head.sha` points at the real head, so reading from disk would
|
|
133
|
+
classify one tree and validate another.
|
|
134
|
+
|
|
135
|
+
## What the gate enforces
|
|
136
|
+
|
|
137
|
+
Per artifact (rules R0 to R10): coherence between findings and residue, counts
|
|
138
|
+
that add up, family decorrelation between generator and reviewer, mandatory
|
|
139
|
+
material evidence in verifiable refutations (`text`, `link` or `hash`) against a
|
|
140
|
+
verifiable target (`verification.against` other than `none`), mandatory human
|
|
141
|
+
attention in interpretive refutations, a fix verified before closing a finding
|
|
142
|
+
in a diff gate, rejection of generic markers (in English and in Spanish), and a
|
|
143
|
+
minimized profile with the free text R9 covers stripped.
|
|
144
|
+
|
|
145
|
+
Per artifact, against the PR: level equal to the repository's declared one (G2),
|
|
146
|
+
Level A blocked while governance is not validated (G3), reviewer confinement
|
|
147
|
+
policy per level (G4), and membership of the reviewed commit in the PR (G5),
|
|
148
|
+
which for the diff gate also requires `base_commit`, because a diff review
|
|
149
|
+
identifies the pair (reviewed base, reviewed head) and not a loose head.
|
|
150
|
+
|
|
151
|
+
Per PR:
|
|
152
|
+
|
|
153
|
+
- **G1**: if the PR touches paths that require review, it adds at least one valid declaration.
|
|
154
|
+
- **G6, coverage**: every changed path is covered by a declaration whose gate the scope policy accepts for that path, and which **qualifies** for it, meaning the path did not change between the reviewed commit and the head. A stale declaration covers nothing.
|
|
155
|
+
- **G7, integration witness**: some declaration saw the complete final tree. Path-by-path coverage is not enough: two side branches reviewed separately and later merged cover every path between them while nobody reviewed the integration.
|
|
156
|
+
- **G8, evidence is append-only**: a PR cannot modify, delete or rename declarations that were already there, nor reuse an existing `event_id`.
|
|
157
|
+
- **G9, new declarations state the current version**: a declaration the PR adds has to declare `residue/v0.3`. Superseded versions are still read so that history is not rewritten; that readability is not a permit to keep emitting under the weaker rules. The evidence plane applies the same criterion at ingestion.
|
|
158
|
+
|
|
159
|
+
The gate **fails closed**: if it cannot resolve the PR range, it does not go
|
|
160
|
+
green. A compliance control that cannot decide does not approve.
|
|
161
|
+
|
|
162
|
+
Honest limit, inherited from the protocol: the machine detects the empty field
|
|
163
|
+
and the generic marker, not the false declaration. Human sampling of merged PRs
|
|
164
|
+
remains the only real defence against cosmetic compliance.
|
|
165
|
+
|
|
166
|
+
## Scope policy
|
|
167
|
+
|
|
168
|
+
Which gate is accepted for each path is declared in the config, and **is always
|
|
169
|
+
read from the current tip of the target branch**, never from the PR checkout.
|
|
170
|
+
From the target and not from the merge-base, which is a different question: the
|
|
171
|
+
merge-base is as old as the branch, so a branch created before the repository
|
|
172
|
+
hardened its policy would drag the old one along. The PR scope is measured
|
|
173
|
+
against the merge-base; the policy that governs is the one the target has today.
|
|
174
|
+
That is why a PR that changes the policy is judged by the previous policy, which
|
|
175
|
+
is correct and also avoids the mutual deadlock of the naive design, where the PR
|
|
176
|
+
that loosens the configuration is rejected by the very rule it wants to change
|
|
177
|
+
and no transition is possible.
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"criticality_level": "B",
|
|
182
|
+
"level_A_enabled": false,
|
|
183
|
+
"gate": {
|
|
184
|
+
"required": true,
|
|
185
|
+
"scope": [
|
|
186
|
+
{ "paths": ["docs/adr/**"], "accepts": ["architecture", "diff"] },
|
|
187
|
+
{ "paths": ["CHANGELOG.md"], "accepts": [] },
|
|
188
|
+
{ "paths": ["**"], "accepts": ["diff"] }
|
|
189
|
+
]
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
The first matching entry wins. `accepts: []` is an explicit exemption, which is
|
|
195
|
+
the governed way out for changelogs or automated PRs. Patterns are anchored at
|
|
196
|
+
the root, `*` does not cross `/`, `**` matches zero or more complete segments,
|
|
197
|
+
and matching is **case sensitive byte by byte** so that the same policy means
|
|
198
|
+
the same thing on any runner. A path that matches nothing requires `diff`: the
|
|
199
|
+
absence of policy is not a permit.
|
|
200
|
+
|
|
201
|
+
**Non-relaxable floor**: the effective configuration path,
|
|
202
|
+
`.github/workflows/**` and the evidence directory always require `diff`,
|
|
203
|
+
whatever `scope` says. Without that floor, an innocent-looking policy such as
|
|
204
|
+
`**/*.yml` with `architecture` would downgrade the workflows, which are the
|
|
205
|
+
source of the control itself.
|
|
206
|
+
|
|
207
|
+
## Deployment requirements
|
|
208
|
+
|
|
209
|
+
This is a requirement, not a suggestion. The gate runs inside the workflow it
|
|
210
|
+
audits, so there is a boundary no code of its own can cross and the platform has
|
|
211
|
+
to resolve:
|
|
212
|
+
|
|
213
|
+
- **Strict required check** (or merge queue) on `pull_request`, so that the check has to correspond to the latest head.
|
|
214
|
+
- **CODEOWNERS** over the effective configuration path (it may not be called `disensor.config.json` if `--config` is used) and over `.github/workflows/`.
|
|
215
|
+
- **Organisation ruleset or required workflow**, defined outside the audited repository.
|
|
216
|
+
- **Pin the Action by SHA**, not by tag: a tag is movable and is not a root of trust. `disensor init` writes the tag of the installed version for convenience, and the generated workflow itself warns that it has to be replaced by the SHA that tag points at. This repository's documentation also uses the tag, because it documents which version corresponds; the SHA is put there by whoever deploys.
|
|
217
|
+
- **Bootstrap**: the first PR that adds the config and the workflow cannot make itself the root of trust. Initial activation is an administrative step, prior to the gate meaning anything.
|
|
218
|
+
|
|
219
|
+
Explicit limit: reading the policy from the base turns a one-step bypass into a
|
|
220
|
+
two-step one, it does not eliminate it. Whoever can merge a relaxation uses it
|
|
221
|
+
on the next PR. And none of this protects against a workflow that was modified,
|
|
222
|
+
skipped or replaced. Only the platform resolves that.
|
|
223
|
+
|
|
224
|
+
## What it does not do
|
|
225
|
+
|
|
226
|
+
It runs no models, asks for no API keys in CI, and no code travels to any
|
|
227
|
+
service: it validates a JSON that is already versioned in the repo. Orchestrating
|
|
228
|
+
the loop lives where the team already works; the artifact's `minimized` profile
|
|
229
|
+
is meant for environments where the text of the findings cannot leave.
|
|
230
|
+
|
|
231
|
+
In the `minimized` profile, R9 strips the finding fields the protocol defines,
|
|
232
|
+
`text` and `link` from every piece of evidence, the residue item `description`,
|
|
233
|
+
and a `repository` that starts with `http`. The schema also requires every value under
|
|
234
|
+
`extensions` to be opaque (a `sha256:` hash, a number, a boolean, `null`, or
|
|
235
|
+
containers of those) and every key to have the shape of an identifier: a name,
|
|
236
|
+
not a message.
|
|
237
|
+
|
|
238
|
+
**The profile narrows the leak channel; it does not close it.** R9 does not
|
|
239
|
+
reach every string in the artifact. `residue.declaration`, `event.pr`,
|
|
240
|
+
`verification.detail`, `human_arbiter.id` and `lead_acceptance` are some of the
|
|
241
|
+
fields that still admit free prose, and the list is not meant to be exhaustive:
|
|
242
|
+
read the schema for the current surface. Note that a hashed `repository` does
|
|
243
|
+
not help if `event.pr` carries the URL. The schema says as much about the
|
|
244
|
+
extension space: an identifier-shaped key can still carry a message. Treat
|
|
245
|
+
`minimized` as a reduction of surface, not as a guarantee that nothing leaves.
|
|
246
|
+
|
|
247
|
+
## Conformance between implementations
|
|
248
|
+
|
|
249
|
+
`spec/vectors/` holds the conformance vectors: 31 artifacts with their expected
|
|
250
|
+
verdict (valid or not, and the rule labels that must fire). Every validator
|
|
251
|
+
implementation has to pass them identically: the Python reference runs them in
|
|
252
|
+
its suite (`tests/test_vectors.py`) and the TypeScript port of the evidence
|
|
253
|
+
plane runs them with `npm run conformidad`. Labels are compared, not messages.
|
|
254
|
+
The vectors are regenerated with `python -m disensor.vectors spec/vectors`.
|
|
255
|
+
|
|
256
|
+
`plano-evidencia/` holds the ingestion Worker (Cloudflare Workers plus D1) with
|
|
257
|
+
the TypeScript port of the validator and the append-only integrity receipt. See
|
|
258
|
+
its README for verification status and deployment.
|
|
259
|
+
|
|
260
|
+
## Glossary EN-ES
|
|
261
|
+
|
|
262
|
+
The contract (schema keys and enums, CLI) has been English since v0.2. The
|
|
263
|
+
method paper is in Spanish, so this maps the contract you read here to the
|
|
264
|
+
terminology you will find there:
|
|
265
|
+
|
|
266
|
+
| Schema/CLI (EN) | Paper (ES) |
|
|
267
|
+
|---|---|
|
|
268
|
+
| residue | residuo |
|
|
269
|
+
| finding | hallazgo |
|
|
270
|
+
| gate (plan, diff, architecture) | compuerta (plan, diff, arquitectura) |
|
|
271
|
+
| criticality_level | nivel de criticidad |
|
|
272
|
+
| profile full / minimized | perfil completo / minimizado |
|
|
273
|
+
| actors: generator, reviewers, human_arbiter | actores: generador, revisores, árbitro humano |
|
|
274
|
+
| family | familia (de modelo) |
|
|
275
|
+
| confinement (permissions, sandbox, read_only_by_instruction, no_confinement) | confinamiento (permisos, sandbox, solo lectura por instrucción, sin confinamiento) |
|
|
276
|
+
| prompt_hash | consigna (hash de la consigna adversarial) |
|
|
277
|
+
| final_state: incorporated, debt_recorded, owner_decision, refuted_verifiable, refuted_interpretive, escalated_open | estado final: incorporado, deuda registrada, decisión del dueño, refutado verificable, refutado interpretativo, escalado abierto |
|
|
278
|
+
| residue classes: escalation_without_decision, principal_refutation, execution_gap | clases de residuo: escalado sin decisión, refutación del principal, gap de ejecución |
|
|
279
|
+
| abbreviated_path / protected_cases_touched | ruta abreviada / casos protegidos |
|
|
280
|
+
| fix_verification | verificación de la corrección |
|
|
281
|
+
| lead_acceptance | aceptación de referente |
|
|
282
|
+
| declared_absence / declaration | ausencia declarada / declaración |
|
|
283
|
+
| metrics: counts, valid, false_positives | métricas: conteos, válidos, falsos positivos |
|
|
284
|
+
|
|
285
|
+
Migrating from v0.1: rename `.residuo/` to `.residue/`, the config keys
|
|
286
|
+
(`nivel_criticidad` to `criticality_level`, `nivel_A_habilitado` to
|
|
287
|
+
`level_A_enabled`) and the artifact keys according to the glossary. The
|
|
288
|
+
validator recognises v0.1 artifacts and says so explicitly; the gate loudly
|
|
289
|
+
rejects a config with old keys instead of applying defaults in silence.
|
|
290
|
+
|
|
291
|
+
## Schema migration: residue/v0.2 to residue/v0.3
|
|
292
|
+
|
|
293
|
+
Mind the ambiguity: this section is about the version **of the schema**; the
|
|
294
|
+
next one is about versions **of the package**. They are two different numberings.
|
|
295
|
+
|
|
296
|
+
v0.3 renames no keys and adds none. It hardens the points where the declared
|
|
297
|
+
guarantee was stronger than the implemented one (three found before the round
|
|
298
|
+
and two that v0.3's own adversarial round added), and adds one value to an enum:
|
|
299
|
+
|
|
300
|
+
| Used to be valid | Now rejected | Why |
|
|
301
|
+
|---|---|---|
|
|
302
|
+
| `refuted_verifiable` with `evidence: {}` | The evidence object has to carry `text`, `link` or `hash` | v0.2 required the object to be present, not its content: a finding could be closed without touching the code by declaring empty evidence ([#5](https://github.com/NicolasRocchia/disensor/issues/5)) |
|
|
303
|
+
| `refuted_verifiable` with `verification.against: "none"` | `against` has to be `repository`, `execution` or `external_source` | Refuting without having verified anything is a contradiction, not a refutation ([#5](https://github.com/NicolasRocchia/disensor/issues/5)) |
|
|
304
|
+
| `minimized` profile with free text in `extensions` | Every value under `extensions` has to be opaque: `sha256:` hash, number, boolean, `null`, or containers of those | The extension space is not interpreted by the rules, so text parked there left the environment while the profile claimed nothing left ([#8](https://github.com/NicolasRocchia/disensor/issues/8)) |
|
|
305
|
+
| `refuted_verifiable` with evidence present but blank (`link: ""`, `text` of pure whitespace) | `text` and `link` have to carry at least one non-blank character | Presence without content reopened the [#5](https://github.com/NicolasRocchia/disensor/issues/5) hole through the weakest leg of the `anyOf`; v0.3's own adversarial round caught it |
|
|
306
|
+
| `minimized` profile with free text in the **keys** of `extensions` | Every key under an opaque object has the shape of an identifier (`[A-Za-z0-9._:-]`, at most 128) | An opaque value is not enough if the message travels in the name: [#8](https://github.com/NicolasRocchia/disensor/issues/8) closed the values and left the keys |
|
|
307
|
+
|
|
308
|
+
And `verification.against` now accepts **`external_source`**: literature,
|
|
309
|
+
third-party specifications, advisories or external documentation. In v0.2 a
|
|
310
|
+
verification against an external source had no truthful category available and
|
|
311
|
+
had to be declared as `repository`
|
|
312
|
+
([#7](https://github.com/NicolasRocchia/disensor/issues/7)).
|
|
313
|
+
|
|
314
|
+
**How to migrate**: set the `schema` field to `residue/v0.3` (the key stays; its value changes). If the artifact
|
|
315
|
+
already satisfies the invariants in the table, there is nothing else to do: no
|
|
316
|
+
fixture of this repository that was valid under v0.2 needed correcting. The
|
|
317
|
+
conformance vectors do include artifacts that violate them, on purpose, as
|
|
318
|
+
negative cases. The validator
|
|
319
|
+
recognises a v0.2 artifact and explains what v0.3 hardened instead of merely
|
|
320
|
+
saying the `const` failed.
|
|
321
|
+
|
|
322
|
+
**Why the identifier was raised instead of hardening v0.2 in place**: not for
|
|
323
|
+
compatibility, of which there was none to protect. It was because the whole
|
|
324
|
+
product rests on a schema identifier meaning one thing; if v0.2 meant something
|
|
325
|
+
different depending on when it was read, the tool would contradict itself in its
|
|
326
|
+
own repository.
|
|
327
|
+
|
|
328
|
+
The original v0.2 contract stays frozen, byte for byte as published, in
|
|
329
|
+
`spec/residue.schema.v0.2.json`: the current schema still reads v0.2, but the
|
|
330
|
+
document that identifier points at no longer depends on a reconstruction.
|
|
331
|
+
|
|
332
|
+
## Migrating from v0.3 to v0.4 (package versions)
|
|
333
|
+
|
|
334
|
+
The artifact schema does not change and already-versioned declarations remain
|
|
335
|
+
valid: what changes is which PRs the gate approves. Updating without reading
|
|
336
|
+
this leaves CI red with messages that do explain the cause, but it is worth
|
|
337
|
+
knowing beforehand.
|
|
338
|
+
|
|
339
|
+
**What starts failing and why:**
|
|
340
|
+
|
|
341
|
+
| Used to pass | Now fails | What to do |
|
|
342
|
+
|---|---|---|
|
|
343
|
+
| Checkout without `fetch-depth: 0` (the gate warned and approved anyway) | The gate cannot resolve the PR range and **fails closed** | Add `fetch-depth: 0` to the checkout. A control that cannot decide does not approve. |
|
|
344
|
+
| A `diff` gate declaration without `base_commit` | Rejected | Fill it in. A diff review identifies the pair (reviewed base, reviewed head), not a loose head. |
|
|
345
|
+
| An artifact with any file name | Rejected | The file is called `<event_id>.json` and the `event_id` has to be a canonical UUID. `disensor new` already generates them that way. |
|
|
346
|
+
| A config with unknown keys or of the wrong type | Rejected | The configuration is validated against a closed schema. `level_A_enabled: "false"` in quotes no longer enables Level A by being a non-empty string. |
|
|
347
|
+
| A declaration from an earlier PR was enough to approve the current one | Rejected | Each PR declares its own. The gate only evaluates what the PR adds. |
|
|
348
|
+
| Declaring `plan` to approve a code change | Rejected | The scope policy says which gate each path accepts, and by default everything requires `diff`. |
|
|
349
|
+
| Reviewing a commit and then continuing to add code | Rejected | The declaration has to cover every path in the state it will be merged in. |
|
|
350
|
+
|
|
351
|
+
**What fixes itself, with nothing to do:** the gate stopped working from the
|
|
352
|
+
second PR onwards, because it also evaluated artifacts from earlier PRs and
|
|
353
|
+
their reviewed commit fell outside the new range. If you were living with that,
|
|
354
|
+
it goes away.
|
|
355
|
+
|
|
356
|
+
**Before updating**, if the repository already has `.residue/` with history, it
|
|
357
|
+
is worth running `disensor gate --no-comment` locally on an open PR to see what
|
|
358
|
+
it says.
|
|
359
|
+
|
|
360
|
+
## Status
|
|
361
|
+
|
|
362
|
+
v0.6.4, on **residue/v0.3**. The long-form documentation is bilingual
|
|
363
|
+
since v0.6.3: `README.md` is the English one that PyPI renders, `README.es.md`
|
|
364
|
+
is the Spanish, and the filling guide ships in both languages. This version
|
|
365
|
+
makes the packaged Spanish guide reachable with `disensor guide --lang es`.
|
|
366
|
+
Releases are published to PyPI via Trusted
|
|
367
|
+
Publishing (OIDC, `release.yml`): no tokens on any machine. v0.4 rewrote the
|
|
368
|
+
gate so that it derives the PR scope from git (see "What the gate enforces") and
|
|
369
|
+
v0.5 ships the packaged adversarial brief with a reproducible hash; the move to
|
|
370
|
+
residue/v0.3 hardens three points of the artifact, closing issues
|
|
371
|
+
[#5](https://github.com/NicolasRocchia/disensor/issues/5),
|
|
372
|
+
[#7](https://github.com/NicolasRocchia/disensor/issues/7) and
|
|
373
|
+
[#8](https://github.com/NicolasRocchia/disensor/issues/8). See "Schema
|
|
374
|
+
migration: residue/v0.2 to residue/v0.3". Decision closed in v0.2: schema keys
|
|
375
|
+
and CLI in English (Spanish remains as CLI aliases). The schema may change up to
|
|
376
|
+
v1.0; changes are declared in the schema itself. Decision open before v1.0: the
|
|
377
|
+
definitive licence (MIT today; Apache-2.0 under consideration for its patent
|
|
378
|
+
grant).
|
|
379
|
+
|
|
380
|
+
## Licence
|
|
381
|
+
|
|
382
|
+
MIT.
|