feat(posture): add deterministic feedback proposals
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02929-244b-7391-b933-c04010e8eedb
This commit is contained in:
parent
dc8da422f8
commit
cfc9e7d0cb
19 changed files with 1428 additions and 16 deletions
145
canon/schemas/posture-feedback-report_v0.1.schema.json
Normal file
145
canon/schemas/posture-feedback-report_v0.1.schema.json
Normal 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}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
|
|
|||
109
canon/standards/posture-feedback_v0.1.md
Normal file
109
canon/standards/posture-feedback_v0.1.md
Normal 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.
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue