feat(posture): add deterministic feedback proposals
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
tegwick 2026-08-23 13:16:34 +02:00
parent dc8da422f8
commit cfc9e7d0cb
19 changed files with 1428 additions and 16 deletions

View file

@ -0,0 +1,145 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://netkingdom.local/schemas/posture-feedback-report_v0.1.schema.json",
"title": "NetKingdom Posture Feedback Report v0.1",
"type": "object",
"additionalProperties": false,
"required": [
"apiVersion",
"kind",
"ok",
"as_of",
"horizon_days",
"fail_on",
"automation",
"summary",
"validation_errors",
"findings",
"report_digest"
],
"properties": {
"apiVersion": {
"const": "netkingdom.io/posture-feedback/v0.1"
},
"kind": {
"const": "PostureFeedbackReport"
},
"ok": {
"type": "boolean"
},
"as_of": {
"type": "string",
"format": "date-time"
},
"horizon_days": {
"type": "integer",
"minimum": 0
},
"fail_on": {
"enum": ["none", "low", "medium", "high"]
},
"automation": {
"type": "object",
"additionalProperties": false,
"required": [
"mode",
"external_write_permitted",
"policy_mutation_permitted",
"declaration_mutation_permitted"
],
"properties": {
"mode": {"const": "proposal-only"},
"external_write_permitted": {"const": false},
"policy_mutation_permitted": {"const": false},
"declaration_mutation_permitted": {"const": false}
}
},
"summary": {
"type": "object",
"additionalProperties": false,
"required": ["total", "high", "medium", "low", "unknown_owner"],
"properties": {
"total": {"type": "integer", "minimum": 0},
"high": {"type": "integer", "minimum": 0},
"medium": {"type": "integer", "minimum": 0},
"low": {"type": "integer", "minimum": 0},
"unknown_owner": {"type": "integer", "minimum": 0}
}
},
"validation_errors": {
"type": "array",
"items": {"type": "string", "minLength": 1}
},
"findings": {
"type": "array",
"items": {
"$ref": "#/$defs/finding"
}
},
"report_digest": {
"type": "string",
"pattern": "^sha256:[0-9a-f]{64}$"
}
},
"$defs": {
"finding": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"class",
"severity",
"source",
"service",
"control",
"owner",
"evidence_state",
"reason",
"recommended_action"
],
"properties": {
"id": {
"type": "string",
"pattern": "^NKFB-[0-9a-f]{16}$"
},
"class": {
"enum": [
"posture-review-overdue",
"posture-review-due-soon",
"zone-review-overdue",
"zone-review-due-soon",
"evidence-freshness-unknown",
"evidence-expired",
"evidence-due-soon",
"implemented-not-evidenced",
"declared-gap"
]
},
"severity": {
"enum": ["low", "medium", "high"]
},
"source": {"type": "string", "minLength": 1},
"service": {"type": "string", "minLength": 1},
"control": {"type": "string", "minLength": 1},
"owner": {"type": "string", "minLength": 1},
"evidence_state": {
"enum": [
"not-applicable",
"due-soon",
"overdue",
"unknown",
"expired",
"implemented",
"gap"
]
},
"due": {
"type": "string",
"minLength": 1
},
"reason": {"type": "string", "minLength": 1},
"recommended_action": {"type": "string", "minLength": 1}
}
}
}
}

View file

@ -21,6 +21,9 @@
"type": "string",
"minLength": 1
},
"responsible_repo": {
"$ref": "#/$defs/repoName"
},
"workload_identity": {
"$ref": "#/$defs/workloadIdentity"
},
@ -36,6 +39,9 @@
"evidence": {
"$ref": "#/$defs/evidence"
},
"evidence_freshness": {
"$ref": "#/$defs/evidenceFreshness"
},
"notes": {
"$ref": "#/$defs/stringList"
},
@ -100,6 +106,16 @@
"required": [
"evidence"
]
},
{
"required": [
"responsible_repo"
]
},
{
"required": [
"evidence_freshness"
]
}
]
}
@ -601,6 +617,76 @@
]
}
},
"evidenceFreshnessEntry": {
"type": "object",
"required": [
"kind",
"observed_at",
"responsible_repo",
"scope",
"remediation"
],
"properties": {
"kind": {
"enum": [
"mechanical",
"adversarial"
]
},
"observed_at": {
"type": "string",
"format": "date-time"
},
"valid_until": {
"type": "string",
"format": "date-time"
},
"responsible_repo": {
"$ref": "#/$defs/repoName"
},
"scope": {
"type": "string",
"minLength": 1
},
"remediation": {
"type": "string",
"minLength": 1
},
"source_revision": {
"type": "string",
"minLength": 1
}
},
"allOf": [
{
"if": {
"properties": {
"kind": {
"const": "adversarial"
}
},
"required": [
"kind"
]
},
"then": {
"required": [
"valid_until"
]
}
}
],
"additionalProperties": false
},
"evidenceFreshness": {
"type": "object",
"patternProperties": {
"^[IAEPRV][0-4]$": {
"$ref": "#/$defs/evidenceFreshnessEntry"
}
},
"additionalProperties": false
},
"service": {
"type": "object",
"required": [
@ -616,6 +702,9 @@
"type": "string",
"minLength": 1
},
"responsible_repo": {
"$ref": "#/$defs/repoName"
},
"workload_identity": {
"$ref": "#/$defs/workloadIdentity"
},
@ -628,6 +717,9 @@
"evidence": {
"$ref": "#/$defs/evidence"
},
"evidence_freshness": {
"$ref": "#/$defs/evidenceFreshness"
},
"notes": {
"$ref": "#/$defs/stringList"
},

View file

@ -0,0 +1,109 @@
---
id: netkingdom-posture-feedback-v0.1
type: standard
title: "NetKingdom Posture Feedback v0.1"
domain: netkingdom
status: proposed
version: "0.1"
owner: net-kingdom
created: "2026-08-23"
updated: "2026-08-23"
last_reviewed: "2026-08-23"
review_interval: 3m
scope: self-optimizing-security
schema:
- canon/schemas/posture-feedback-report_v0.1.schema.json
validator:
- tools/posture-feedback/posture_feedback.py
related:
- canon/standards/tenancy-posture_v0.1.md
- canon/standards/security-zones_v0.1.md
---
# NetKingdom Posture Feedback v0.1
## 1. Purpose
This contract is the first bounded C6 feedback mechanism. It turns explicit
posture review dates, evidence freshness, implemented-but-unevidenced controls,
and declared gaps into deterministic remediation **proposals**.
It does not modify a posture level, policy, declaration, workplan, State Hub,
or runtime. Human or separately governed automation decides whether a proposal
becomes work.
## 2. Deterministic time
Evaluation requires an explicit RFC 3339 `as_of` timestamp. Wall-clock time is
never read implicitly. A date-only `review_due` remains current through that
calendar date in UTC and becomes overdue on the following UTC date.
A non-negative horizon in days identifies items due soon. Changing the horizon
changes the report digest and is therefore visible.
## 3. Owner resolution
The evaluator routes only from authoritative declaration fields:
- posture review, gaps, and implemented controls: `responsible_repo`;
- evidence replacement: `evidence_freshness.<level>.responsible_repo`;
- security-zone review: `zones.responsible_party`.
If the relevant field is absent, owner resolution is `unknown`. The evaluator
must not infer ownership from the service name, repository path, Git remote,
previous work, or another policy subject.
## 4. Finding classes
| Finding | Trigger | Severity |
| --- | --- | --- |
| `posture-review-overdue` | `as_of` is after `tenancy.review_due` | high |
| `posture-review-due-soon` | review is within the horizon | medium |
| `zone-review-overdue` | `as_of` is after `zones.review_due` | high |
| `zone-review-due-soon` | zone review is within the horizon | medium |
| `evidence-freshness-unknown` | a current adversarial level has no complete freshness entry | high |
| `evidence-expired` | `as_of` is after `valid_until` | high |
| `evidence-due-soon` | evidence expires within the horizon | medium |
| `implemented-not-evidenced` | an `implemented` level is above `current` | medium |
| `declared-gap` | a non-empty `tenancy.gap` entry exists | low |
The review horizon does not generate a due-soon finding for an item already
overdue or expired. Exact equality with a timestamp is still valid; expiry is
strictly `as_of > valid_until`.
Current adversarial levels are `E2`, `R4`, and `V2``V4`. This vocabulary comes
from Tenancy Posture §13. Mechanical evidence is evaluated for expiry only when
its declaration explicitly supplies `valid_until`.
## 5. Proposal and safety boundary
Every finding receives a stable id derived from its source declaration,
service, finding class, control, and due value. It contains the authoritative
owner or `unknown`, current evidence state, reason, and recommended action.
For declarations under the workspace containing this repository, the source is
normalized to `<repo>/<path>` so absolute checkout locations do not perturb the
identity. This source normalization identifies an input only; it is never an
ownership inference.
Every report declares:
```yaml
automation:
mode: proposal-only
external_write_permitted: false
policy_mutation_permitted: false
declaration_mutation_permitted: false
```
Expired or unknown evidence does not silently inherit freshness and does not
silently downgrade a level. It makes the uncertainty visible for governed
review. Consumers that use the report as an admission gate may fail closed on
high findings, but that is a separate owner decision.
## 6. Exit behavior
The CLI emits a report conforming to
`posture-feedback-report_v0.1.schema.json`. `--fail-on high` exits non-zero when
at least one high-severity finding exists; `medium` includes medium and high;
`low` includes every finding; `none` reports without a finding-based failure.
Invalid declarations always exit non-zero.

View file

@ -8,7 +8,7 @@ version: "0.1"
created: "2026-08-17"
updated: "2026-08-23"
scope: multi-tenancy-security-framework
revision: "draft-13"
revision: "draft-14"
owner: net-kingdom
last_reviewed: "2026-08-23"
review_interval: 6m
@ -87,6 +87,11 @@ and the tenant-engine boundary contract, not in the work-factory canon.
required by §13.2. The claim remains explicitly bounded and freshness-dated:
the attempted cross-tenant attacks did not work; this is not a universal
isolation proof.
- **draft-14** makes evidence freshness and remediation ownership declarable.
Adversarial evidence may now carry its observation and expiry timestamps,
bounded scope, responsible repository, and replacement action. A separate
proposal-only evaluator treats absent authoritative owner or freshness as
`unknown`; it does not infer either or mutate the declared posture.
**Reviewed by all six. The score:** six repos found three live defects in their
own code by reading the ladders — `tenant-engine`'s unfiltered
@ -826,6 +831,11 @@ declaration but do not replace it. The schema carries `current`, `implemented`,
`target`, `reviewed`, `review_due`, `gap`, `placement_exceptions`,
`service_class` (§8.3), per-path detail (§5.2), and provider reachability
(§5.5), plus the workload identity prerequisite for zone membership (§5.6.2).
It also permits `responsible_repo` for authoritative posture routing and
`evidence_freshness` for machine-readable evidence observation, expiry, scope,
owner, and remediation metadata. Their absence remains valid declaration
syntax; feedback resolution must report `unknown`, never infer them from a
directory, service name, or previous owner.
From the `net-kingdom` repo, owners validate one or more declarations
with `uv run tools/tenancy-posture/validate.py <path>...`; the validator applies
the JSON Schema and the evidence, date, implemented/current and provider-range
@ -1129,6 +1139,22 @@ rather than a green build. Cross-tenant findings are the category external
testing practice identifies as needing human review. **A passing CI run is not
E2 evidence.**
**Decision 13.5 — freshness and ownership are explicit inputs.** A declaration
may attach `evidence_freshness.<level>` to an evidence key. An adversarial entry
requires `observed_at`, `valid_until`, `responsible_repo`, `scope`, and
`remediation`; a mechanical entry may omit `valid_until` when the artifact is
continuously re-established by the referenced revision or CI control. The
timestamps use RFC 3339 and the responsible repository is the authority for
replacement evidence.
The feedback evaluator does not parse prose for dates, infer ownership from a
file path, or silently extend a validity window. A current adversarial claim
without freshness metadata resolves to **freshness `unknown`**. An expired
artifact resolves to **freshness `expired`**. Neither automatically rewrites the
declared level: the evaluator emits a deterministic owner-routed remediation
proposal so review remains observable and controlled. The proposal contract is
`posture-feedback_v0.1`; it performs no State Hub write or policy mutation.
| Level | Evidence | Kind |
|---|---|---|
| **I2** | Identifiers validated against the vocabulary; rejection test for a malformed id; binding shown to come from a verified token | Mechanical |