Skip to content

Devostasis 0.1.9

Latest

Choose a tag to compare

@drevendev drevendev released this 30 Sep 20:04
3bd60db

Four things in one release. First, the adoption of the research judgements
that had been delivered and not adopted, which the roadmap's standing
obligation puts before queued work: one Integrity and one Clutter rule
version, one accepted diagnostic under an existing rule, eight accepted exact
vectors and thirty-one cases of three judgements, plus the vector kinds those
cases needed. Second, the review pass of 2026-09-22 (issue #30). Third, the
repairs of the research audits of 2026-09-20 to 2026-09-24, which were
handed off on Drive and had no record in this repository until the review
of 2026-09-25 catalogued them in issue #35. Fourth, the review of
2026-09-30 (issue #48), which found defects in this release's own new code
(among them an Integrity superset that could omit the band it emitted,
replaced by the accepted totality rule) and older ones in verification,
collection, the store and the command line, and filed what needs a decision
as #38 to #47. No threshold, window or gauge changed; the two band rules
that changed did so under accepted judgements, and their rule_id moved
with them.

  • Integrity rule integrity.bands.v1+ci-unit-004. Four accepted
    judgements, one rule version, one RULE_VERSION_BOUNDARY per project.
    PV-REV-INTEGRITY-UNKNOWN-001 (issue #13): a newest in-scope revision whose
    current verdict is UNKNOWN never inherits an older decisive verdict; the
    Vital is UNKNOWN with no band, the decisive history stays in derived,
    and CURRENT_VERDICT_UNKNOWN:<revision> names the cause. Positively
    observed NOT_EXECUTED and NON_VERIFY_TERMINAL keep the accepted
    fallback. Before this, four passes and a newest startup_failure produced
    CLEAN / AVAILABLE / EXACT, and since 0.1.8 a false IMPROVED.
    PV-REV-TEST-003 (issue #12 finding 3): a required revision series that is
    PARTIAL is UNKNOWN with no band, its counts visible and marked; the
    DEGRADED path with a fixed three-band tail called a superset is gone.
    PV-REV-TEST-VECTORS-002 (issue #21): one to three decisive revisions carry
    sample_strength = SPARSE and CI_SPARSE_SAMPLE, four or more
    ESTABLISHED. PV-INTEGRITY-TOTALITY-001 (accepted by
    PV-REV-INT-TOTALITY-001, #33): a newest revision still being verified is
    spoken for by no older verdict; the history names a band only when it
    already satisfies FAILING on its own (four or more decisive revisions, a
    quarter or more failed), DEGRADED / FAILING / EXACT, and every other
    history is UNKNOWN with no band, diagnosed CI_CURRENT_VERIFY_UNRESOLVED.
    This rule version first carried a possible_bands derived from the
    completions instead; the review of 2026-09-30 found it could omit the band
    it emitted (777 of 7,029 shapes, the commonest being one workflow passed
    and one still running) and could miss bands a completion reaches, and the
    accepted contract defines no reachability algorithm at all. A positively
    unconfigured repository with only non-decisive recent records is
    UNINSTRUMENTED with CI_UNINSTRUMENTED_WITH_RECENT_NONDECISIVE_HISTORY;
    an unusable series is UNKNOWN even beside "not configured"; a verdict
    outside the vocabulary is UNKNOWN, never a fallback to an older pass; a
    record whose contribution contradicts its history is a defect. Cases
    INT-UNKNOWN-01..06, INT-TOTAL-01..06 and 08..13, T2, R1, R2 are
    executable vectors.
  • Pulse diagnoses issue-only activity (PULSE_ISSUE_ONLY_ACTIVITY,
    permanent case T5, issue #22), under pulse.bands.v1 as the accepted vector
    requires: provenance, not a judgement about productivity. It is emitted
    only when every channel was positively observed: with a channel unobserved
    or a required enumeration capped, "every observed event came from issues"
    would be a claim about evidence nobody has.
  • Clutter rule clutter.bands.v1: incomplete evidence is a confirmed burden
    floor, never a manufactured band.
    Adopts PV-CLUTTER-INCOMPLETE-001
    (accepted by PV-REV-CLUTTER-INCOMPLETE-001, cases CLU-INCOMPLETE-01..20,
    all executable) and the reading PV-ISSUE-026-RECONCILE-001 gave issue
    #26. An explicitly UNAVAILABLE issue or branch component, or a PARTIAL
    count with an observed subset, no longer yields a band from the rest: the
    band is the floor the observed facts prove. Observed stale work and
    classified stale branches prove it; the ratio proves it only over a
    complete issue and change-request domain; an UNCLASSIFIED branch count
    proves nothing. A PARTIAL count counts as an observed subset only when
    its coverage says so (complete = false,
    value_semantics = OBSERVED_SUBSET_COUNT, which the derivation now records
    on every count aggregate of an incomplete inventory and on nothing else);
    without that proof, with other semantics or with malformed coverage the
    component is unresolved and diagnosed CLUTTER_PARTIAL_NOT_TRUSTED
    (review PV-REV-PR-031-003, cases CLU-PARTIAL-TRUST-01..08; before, any
    fresh PARTIAL value was promoted to a floor on its status alone, so a
    saved observation set could prove HEAVY with an estimate).
    HEAVY is DEGRADED / EXACT (terminal), CLUTTERED and
    LIGHT are DEGRADED lower bounds with a conservative superset, and a
    floor of nothing is UNKNOWN with no band. Before, an unavailable
    component with nothing else observed produced DEGRADED CLEAN, a band
    made from absence; on the fleet's store that is exactly one project, whose
    issues are disabled and which has no stale residue: it becomes UNKNOWN,
    which is what the evidence supports. The #26 case, a capped branch head
    resolution, proves a floor only with explicit CLASSIFIED retention
    semantics (>= 20 HEAVY exact, 6..19 CLUTTERED, 1..5 LIGHT, zero
    UNKNOWN); the GitHub adapter does not emit retention semantics, so on
    GitHub that case stays UNKNOWN until issue #22 decides whether it should.
    Permanent case T7 (issue #22), the UNCLASSIFIED upper bound with
    CLUTTER_BRANCH_PURPOSE_UNCLASSIFIED, moves under the same rule id
    unchanged, and when the work items alone reach the band the full count
    reaches, that band is DEGRADED / EXACT as the contract's section 5 says.
    One RULE_VERSION_BOUNDARY on Clutter per project; no threshold or window
    moved. The GitHub adapter still does not emit retention_semantics:
    emitting UNCLASSIFIED would make Clutter DEGRADED for every project with
    a stale branch and take the accepted ordering away from it, so that is a
    fleet-wide decision left with issue #22, not a default.
  • The accepted exact vectors are in the corpus as the research process
    wrote them: T2, R1, R2 (PV-REV-TEST-VECTORS-002), R4
    (PV-REV-TEST-VECTORS-004), R3 (PV-REV-TEST-VECTORS-005), T4, T5, T7
    (PV-REV-TEST-VECTORS-007). Seven of the seventy named cases without a test
    now have one; sixty-three remain.
  • Two vector kinds and one shape, in answer to the format findings. The
    ci kind (issue #20) starts at the provider-native normalization: workflow
    runs, their earlier attempts and check suites as the provider reports them,
    through the outcome map to canonical revision records and, when the case
    asks, into Integrity. R5..R10 can now be materialized at the boundary they
    are about; INT-UNKNOWN-02 and 03 already are. The activity kind
    carries ACT-COV-01..05 of PV-REV-ACTIVITY-COVERAGE-001 (issue #9), the
    runtime of which 0.1.7 already had. variants lets one case hold several
    evidence shapes to one expectation, for vital and ci cases, which is
    the one-identifier multi-variant mechanism T8 and R9 need (issue #23);
    vital cases may also assert shared_signal_groups and
    dependency_group_ids (part of T6). Every kind fails closed as before; a
    provider without a normalization is an error, never a skip.
  • An observation not later than the previous bundle is INCOMPARABLE
    (issue #17, NON_MONOTONIC_OBSERVATION:<previous>-><current>). It was
    COMPARABLE, its delta reported IMPROVED and WORSENED with the
    direction inverted while citing the accepted order, and activity.json
    carried an interval that ended before it started. The bundle is still
    written and immutable; it claims no direction and no interval, and
    build_activity refuses a backwards interval outright. Where such a bundle
    lands is the monotonic-write policy of issue #12 finding 6, still to be
    decided.
  • The comparison reads the immutable bundle the index names, not
    latest/
    (issue #28). A compacted or damaged convenience copy no longer
    turns the next run into a HISTORY_GAP; a copy that names a different
    bundle than the index is still refused, so finding 6 stays visible. Without
    an index the newest immutable bundle is found by scanning. The fleet
    surfaces link to the immutable report when the copy is gone. RPT-3 now
    corrupts the immutable copy, which is what its sentence always meant.
  • Verification binds the manifest's metadata to what the identity hashes
    (issue #12 finding 4). semantic_config, the field the comparison reads,
    must be the projection of the validated stored config
    (SEMANTIC_CONFIG_MISMATCH); every identity field the manifest repeats must
    be present in both copies and agree with the preimage
    (IDENTITY_FIELD_MISMATCH), with the fields a bundle must carry and the
    renderers it may name decided by its exact lineage
    (UNSUPPORTED_ARTIFACT_LINEAGE, RENDERER_VERSION_NOT_IN_LINEAGE;
    PV-AUDIT-MANIFEST-PREIMAGE-BINDING-001, whose concrete case was a bundle
    that lost its manifest renderer_version, kept its id, skipped the report
    replay and verified with any report at all); the manifest receipt
    must hash to source_receipts_digest and be the receipt inside
    observations.json (RECEIPT_DIGEST_MISMATCH, RECEIPT_COPY_MISMATCH);
    and snapshot.json must name the evidence the bundle carries
    (OBSERVATIONS_DIGEST_MISMATCH). A manifest of the wrong shape is a
    problem, not an AttributeError. All 308 bundles of the fleet's store
    (both lineages, all four renderers) still verify.
  • Verification holds the whole identity, not only what a forger left in
    place
    (review of 2026-09-30). The preimage must have exactly its
    lineage's field set (IDENTITY_PREIMAGE_SHAPE_MISMATCH): without
    source_receipts_digest the receipt binding was simply skipped. Every
    member the preimage hashes must be declared (IDENTITY_MEMBER_NOT_DECLARED):
    a bundle that dropped delta.json or snapshot.json together with its
    entry kept its id, stopped the replay and verified with any report. The
    adapters line the report prints must be the provider and collector the
    identity binds (ADAPTERS_MISMATCH). And verify no longer says "report
    reproducibility all match" for a report it did not replay: the 74 stored
    bundles written by renderers v1 to v3 are bound by their report digest and
    are now reported as such; replaying them needs the historical renderers,
    which this version does not carry.
  • Collection no longer claims completeness it did not have (review of
    2026-09-30). A filtered /actions/runs query stops at 1,000 results and
    answers the next page empty; that empty page was read as the end, so a busy
    repository's oldest runs (and their failures) vanished from an AVAILABLE
    series. The listing is now read against the provider's total_count and
    is PARTIAL when it ends short. A listing that repeats a commit or a run
    between pages (a push during pagination) counts it once and is PARTIAL
    with LISTING_SHIFTED; it used to count it twice as complete evidence. A
    revision whose check-suite page the budget refused was counted as
    examined, which could make ci.configured a positive false; it is now
    UNKNOWN / SAMPLE_INCOMPLETE. Failures that escaped the inventory boundary
    and cost the project its bundle are now declared: http.client
    exceptions, a timeout while reading an error body, JSON nested past the
    decoder's depth, instants outside the representable range (a register
    date of 0001-01-01T00:00:00+01:00), register files with non-string
    content, no size, invalid base64 or more bytes than the bound, and run or
    suite fields (name, event, html_url, workflow_id, url) of the
    wrong type, which failed the canonical encoder.
  • Workflows, documentation and tests (review of 2026-09-30).
    released-pins.yml no longer runs on push: the file reaches master only
    in a release merge, the one moment its pins cannot resolve, and its single
    push run (a8726f5, 72 seconds before v0.1.8 was tagged) kept this
    repository's own Integrity FAILING for two weeks. The reusable
    observe-self.yml passes devostasis-ref to the shell through the
    environment instead of interpolating it, and trims debt labels without
    xargs, which rewrote quotes and backslashes and failed on an apostrophe.
    The README's self-observation snippet carries the permissions block it
    needs (a called workflow can only narrow the caller's token, so without it
    GitHub refuses the run), and deployment.md says that instead of promising
    FORBIDDEN Vitals; its cache snippet keeps the token, id and
    continue-on-error of the step it extends; the store layout names a
    .gitattributes so a Windows clone does not break every report digest.
    build's CONFIG_MISMATCH says which part of the configuration it can
    and cannot be passed. The example fleet table, the vector page, debt D-1,
    CONTRIBUTING's case families and the --now paragraph (which promised
    what #19 still breaks) say what is true now. The suite no longer sleeps
    three real seconds, runs from any working directory, and the pin guards
    are case-insensitive; one register assertion that could not fail now can.
  • Vitals and vectors (review of 2026-09-30). Pulse crashed with
    IndexError when a capped commit enumeration covered all 29 UTC dates a
    28-day window touches (the completion grid stopped at 28 and was empty),
    which cost the project its bundle; the grid now reaches 29 and includes
    the one-event QUIET threshold it missed, and an empty set would be
    UNKNOWN as PV-PULSE-REQUIRED-LOWER-BOUND-001 says. Debt read a stale
    or freshness-unknown partial register as a PRESENT lower bound; it is
    UNKNOWN, as vitals.md always said. The vector runner refuses an
    expectation that states nothing (an empty object or list, an empty code,
    which as a prefix matched every code), and the published vector schema
    requires status in its value conditional: without it the schema rejected
    39 of the 64 vectors the runner accepts. vitals.md lists all seven rule
    ids (it said four Vitals kept their V0 ids; Clutter moved in this
    release), and a drift test now reads them from the code.
  • The store and the command line tell the truth about what they did
    (review of 2026-09-30). Without an index, the scan for the newest bundle
    skipped a directory whose manifest it could not read, so the next run
    compared against an older bundle, or called a store with history
    BASELINE; such a directory now makes the state unverified and the
    comparison a HISTORY_GAP (INDEXLESS_CANDIDATE_UNREADABLE). A
    repository named index.json was read as a project index and broke every
    identity lookup; index files are now found at their exact depth. Problem
    texts carried absolute store paths and exception messages into the delta
    of a HISTORY_GAP bundle, so the same damage gave a different bundle id
    per checkout (and a local path inside a committed store); they are now
    store-relative and name exceptions by type. The fleet surfaces are moved
    into place instead of being truncated first. run prints every project's
    result before a fleet-surface failure, and build its committed bundle; a
    conditional cache that cannot be saved is a warning, written after the
    fleet surfaces, not a traceback that skips them; and evaluate, build,
    render, gauges, demand and actions-summary answer a missing or
    unreadable input with input error and exit 2.
  • A build over evidence derived under another configuration is refused
    (issue #27, CONFIG_MISMATCH). observe records the digest of the
    configuration its aggregates were derived under; build with different
    planning or debt options produced a verified bundle whose effective config
    said one thing and whose snapshot said another. Only a real digest is
    compared, so fixtures and examples with placeholders are unaffected.
  • Check-suite coverage is tracked, paginated and reported (issue #12
    finding 1). Suites are read page by page, and the series records how many
    revisions were planned and examined, whether every page was read and why
    sampling stopped. Past 100 revisions, past 3 pages, after a failed fetch or
    a spent budget the series is PARTIAL / CHECK_SUITES_INCOMPLETE; the
    parents already collected are kept and an observed failure stays. The 101st
    revision and the 101st suite are tests.
  • The example bundle was regenerated under the new Integrity and Clutter
    rules.

The research audits of 2026-09-20 to 2026-09-24, each reproduced here before
it was repaired (the review of 2026-09-25 found twenty-six malformed
successful payloads escaping the collectors with a probe, and the rest by
the audits' own constructions):

  • The index tail is bound to this project's history (review of #28's
    repair on this branch). The tail is followed only along the canonical
    history/YYYY/MM/DD/<bundle_id> path, resolved inside this project's
    history/ tree, with a basename equal to the id it claims, and the bundle
    found there must carry the identity the index records; a damaged index
    that names another project's bundle, by a parent reference, an absolute
    path or a copy inside the tree, is INDEX_TAIL_INVALID or
    PROJECT_IDENTITY_MISMATCH, a HISTORY_GAP and never a comparison. A
    malformed index is a store failure and is never appended to.
  • The store fails closed on what it reads about itself. An unreadable
    index met while proving an immutable id lives nowhere else fails the lookup
    instead of passing for absence (PV-AUDIT-HISTORYSTORE-001); an
    unreadable or malformed project index, an unfollowable tail or a demand
    member that is present but unreadable stops the fleet surfaces instead of
    dropping the project, and the run and index commands report a store
    error (PV-AUDIT-FLEET-INDEX-001, PV-AUDIT-FLEET-COVERAGE-001); a
    wrapper whose bundle_id or project_key disagrees with its manifest is
    refused before any write (PV-AUDIT-STORE-BUNDLE-PATH-BINDING-001,
    PV-AUDIT-STORE-PROJECT-BINDING-001).
  • Publication is recoverable. The index is replaced through a temporary
    file, never truncated in place; it is written before the convenience copy,
    and the previous copy stays until the new one is in place, so an
    interruption leaves a stale copy of an indexed bundle that the next commit
    replaces rather than a gap. A copy of a bundle the index does not know, or
    one observed after the tail, is still refused
    (PV-AUDIT-HISTORYSTORE-ATOMIC-PUBLICATION-001).
  • A locator is a name, not a path. A project key derives a store path
    only as a <forge>/<owner>/<repo> triple of letters, digits, dots, hyphens
    and underscores, strictly beneath projects/, and the configuration admits
    only such owner and repository names, so C:\escape/widget or ../x never
    reaches the filesystem (PV-AUDIT-STORE-PATH-001). Two spellings of one
    repository are one project in the configuration, and once the provider has
    named the repository, in the run: the second is refused with
    DUPLICATE_PROJECT_IDENTITY before it can collect twice or be mistaken for
    a rename (PV-AUDIT-PROJECT-LOCATOR-ALIAS-001). activity.enabled must be
    a boolean and store an object (PV-AUDIT-CONFIG-SHAPE-001).
  • Every successful payload is validated before it is read
    (PV-AUDIT-GITHUB-REPO/COMMITS/CR/ISSUES/BRANCH/RELEASE/CI-PAYLOAD-001).
    The two shapes 0.1.9 already caught were members of a family: every
    collector dereferenced its 200 after the boundary that turns failed
    requests into observations, so a scalar row, a missing field or an
    unreadable timestamp escaped as a Python exception and the project produced
    no bundle. Now every consumed field is typed evidence; a body that does not
    establish it is ERROR / UNEXPECTED_PAYLOAD for that inventory alone, the
    other inventories are still collected, and nothing is coerced, defaulted
    or skipped: no guessed main for a repository without a default branch,
    no truthiness of the string "false", no commit or published release
    silently omitted for lacking a date, no run skipped for lacking a head, no
    check-suite count assumed, and an attempt that names another run or another
    number is not this run's history. Repository metadata that does not
    establish the routing facts fails the project explicitly. A branch head
    whose detail cannot be read stays unresolved, PARTIAL / BRANCH_HEADS_UNRESOLVED, never stale or fresh.
    (docs/spec/github-adapter.md has the table.)
  • A register state outside the vocabulary is an invalid register, not an
    open item (PV-AUDIT-REGISTER-STATE-001): clsoed, 17 or false no
    longer manufacture an open target or debt item.
  • A wait hint that cannot be read is no hint (PV-AUDIT-GITHUB-RETRY-HEADER-001):
    Retry-After: inf or an overflowing reset epoch used to escape as
    OverflowError; the bounded backoff applies and the answer's own
    classification stands.
  • A redirect never carries the token off the API origin
    (PV-AUDIT-GITHUB-REDIRECT-AUTH-001). urllib copies Authorization onto
    a redirected request, to any host; the transport now follows redirects only
    to the configured API origin and refuses the rest as REDIRECT_REFUSED. A
    renamed repository still resolves.
  • The entity-tag cache is devostasis.http-cache.v2
    (PV-AUDIT-GITHUB-CACHE-INTEGRITY-001): an entry is replayed after a 304
    only when its complete shape is readable and its body still hashes to the
    digest stored beside the tag; a corrupt entry or a previous cache file is a
    miss and one unconditional refetch, never a replayed body and never a
    ValueError while loading. The first fleet run after the upgrade pays a
    full quota once.
  • The canonical decoder is as strict as the writer
    (PV-AUDIT-CANONICAL-NONFINITE/DECIMAL/UNICODE/JSON-PARSER-001): decimal
    and exponent numbers, NaN and the infinities, an object naming a member
    twice, and an unpaired surrogate are refused when read, as a
    canonicalization error, instead of becoming a host value that a later
    check may or may not catch. All 308 bundles of the fleet's store still
    read and verify.

The review pass of 2026-09-22, each defect reproduced before it was fixed:

  • A successful response of the wrong shape costs one inventory, not the
    project.
    GET /actions/runs answering 200 with no body, or
    GET /releases answering an object instead of a list, escaped as
    AttributeError; run_all's boundary caught it, so the project produced no
    bundle at all. Both are now ERROR / UNEXPECTED_PAYLOAD on that inventory
    alone, like every other provider failure, and the rest of the evidence is
    still collected. The review of this change (PV-REV-PR-029) found the same
    hole one endpoint over: GET /actions/workflows answering 200 with a body
    that is not an object, or a total_count that is not a non-negative
    integer, raised past the WORKFLOWS_UNAVAILABLE fallback. The count is now
    validated before it is read; a malformed one is UNEXPECTED_PAYLOAD, the
    receipt says WORKFLOWS_UNAVAILABLE:UNEXPECTED_PAYLOAD, check suites are
    sampled as for any other failed lookup, and no count is invented.
  • A failed workflow lookup is no longer silent. When
    GET /actions/workflows fails (a token without actions: read is enough)
    the collector samples check suites instead, Actions-created suites
    included, and the evidence is parent-level. That was correct and invisible:
    the receipt said CI_SURFACE:GITHUB_CHECK_SUITES_SAMPLED and nothing about
    why. It now also carries WORKFLOWS_UNAVAILABLE:<reason>, mirroring
    CHECK_SUITES_UNAVAILABLE. Capability notes are identity-bearing, so a
    project in exactly that state gets a new bundle id once; every other bundle
    is unchanged.
  • The build metadata names the setuptools that can read it.
    pyproject.toml uses PEP 639 (license = "MIT", license-files) but
    required only setuptools>=69. setuptools 76 rejects the file
    (project.license must be valid exactly by one definition); isolated builds
    always fetched a newer setuptools, which is why CI never saw it, and
    --no-build-isolation did. Now setuptools>=77.
  • The documentation described a token order the code never used. The
    configured token_env is read first, before DEVOSTASIS_GITHUB_TOKEN,
    GITHUB_TOKEN and GH_TOKEN; the configuration guide and the adapter page
    said last. The code was right, since a runner's own GITHUB_TOKEN must not
    override the token a fleet configuration names, and the pages now say so.
  • Bookkeeping: the README status paragraph still described 0.1.0; the
    roadmap listed issue #9 as "filed, not yet indexed" after it had been
    answered, and had no row for the accepted judgements this repository has
    not adopted (#13, #19, #21, #12 finding 3, the cases of #9). It has one now,
    under the standing obligation that names them. Three findings of the same
    review pass are filed as issues rather than fixed here, because each is a
    rule or a contract decision: Clutter UNKNOWN past the branch lookup cap
    (#26),
    devostasis build accepting flags that disagree with the receipt
    (#27), and
    comparability decided from the convenience copy latest/
    (#28).