# audit-core tenancy posture # # Declared per NetKingdom Tenancy Posture v0.1 (draft-8), # net-kingdom/canon/standards/tenancy-posture_v0.1.md. # Location and schema per Decision 5.4; per-path detail per Decision 5.2; # provider block per Decision 5.5. # # Conformance is accuracy, not altitude (§6). Nothing here is claimed above # what this repo can evidence today. E is deliberately declared lower than the # mechanism in place because the adversarial artifact is still absent. schema_version: "0.1" framework: netkingdom-tenancy-posture service: audit-core role: tenant-audit-service tenancy: reviewed: "2026-08-17" review_due: "2027-02-17" service_class: batch # §8.3.2. Co-resident with latency-critical # tenant-engine on platform-pg; the mixture is # reported by the platform, not hidden. current: { I: 1, A: 2, E: 1, P: 1, R: 2, V: 0 } implemented: { E: 2 } target: { I: 1, A: 2, E: 3, P: 1, R: 2, V: 1 } # §5.2 — declare per path, quote the minimum. The quoted E above is the # minimum across paths. As of AUDIT-WP-0008-T04 both paths carry the same # mechanism; the quoted level stays at 1 for the evidence reason in gap.E, # not because a path is weaker. paths: E: write: 2 # Sender credential bound to the sources and tenants it may # claim; checked at one choke point (audit_core/ingestion.py). read: 2 # Event reads filtered to permitted tenants; surfaces with no # tenant key require full scope. Cross-tenant fetch returns # 404, not 403, so the surface is not an existence oracle. placement_exceptions: [] # No tenant is on dedicated substrate. All # tenants share database audit_core on # platform-pg. gap: I: >- I1 is accurate and is not currently a target. Senders authenticate with bearer tokens and the tenant arrives in the request body, checked against an allowlist bound to the credential (§4.1 places request-supplied tenant identifiers at I1 however canonical the string). The allowlist is a real control, but it is an A-axis control; it does not make the identity verified. Moving to I2 means senders carrying a verified token with a tenant claim, which is a change to every sender and not audit-core's to make alone. Recorded as accurate, not as ambition. A: >- A2 is accurate. Authorization is a single local boundary — permitted sources, permitted tenants, may_write, may_read — bound once and centrally. No flex-auth delegation. A3 is not a near-term target: flex-auth is itself at A0 on its own self-report (/v1/check authenticates no caller), so delegating to it today would lower this service's assurance, not raise it. E: >- The E2 mechanism is in place on both paths as of AUDIT-WP-0008-T04, and the quoted level is still 1. This is deliberate. §13.2 states that a passing CI run is not E2 evidence: the E2 artifact is adversarial, needs separate tenant contexts compared against each other, and carries a review date rather than a green build. The repo's cross-tenant tests are mechanical. Under §13.1 the level is not claimable until that artifact exists, so E stays at 1 until AUDIT-WP-0008-T05 produces it with whitehat-security. Declaring E2 on the strength of unit tests would be the overclaim §6 prohibits, and the read-path defect this repo just fixed was found precisely by refusing that kind of reasoning. E_target: >- E3 (row-level security per rapp-postgres ADR-0003) targeted 2027-03-31. Blocked behind the E2 artifact — §4.3 requires E2 evidence alongside any E3 claim — and needs the EXPLAIN comparison first, since RLS disables functional indexes built on non-leakproof functions. E4 is unreachable at P1 by the §3.2 coupling and is not a target. R_ceiling: >- R4 is unreachable under the current design and is not a target. Per Decision 4.5.3 — which this repo found — the hash chain commits to a SHA-256 of the cleartext record, which survives key destruction as a confirmation oracle over low-entropy fields. A fleet R4 target must exempt this service explicitly. See docs/erasure-and-audit.md (AUDIT-WP-0008-T03). credentials: >- Stated gap against Decision 9.2 rather than a silent exclusion. Database credentials comply with 9.1: dynamic leases re-read from a mounted Secret at connection time, no restart on rotation. Ingest credentials do not: they are static long-lived bearer tokens, rotated overlap-first by publishing a replacement alongside the incumbent and then dropping the predecessor. audit-core raised this omission during review and is not exempting itself from the rule it asked for. No dated remedy yet; leasing consumer-facing credentials needs a broker path that does not exist. retention_placement: >- audit-core's INTENT wants unbounded WORM archive. That is data.archive in ITC-CAP terms, recorded as an unmet requirement in data/capability/audit-core-operational.json, and it is deliberately not a backup-window question: a 30-day WAL window is not an archive, is not searchable, and restores instance-wide. Reviewed 2026-12-31. If no data.archive provision is procured by then, audit-core reopens placement under the §4.5 retention trigger, with P2 as the fallback — a worse answer than archive, named now so it is not improvised later. V: >- No exercise establishes restart recovery for the complete audit path. V1 is the target; replica count or Kubernetes restart policy is not treated as evidence. Dependencies enumerated and the exercise specified in docs/availability-recovery.md (AUDIT-WP-0008-T07); the exercise itself awaits a live window on railiance01. V2 is not reachable at P1 as built: platform-pg runs instances 1, and Decision 4.6.1 makes V the minimum across synchronous providers, so V1 is the ceiling here rather than the next step. provider: capability: operations.audit profile: administrative axes: E: available: 2 maximum: 3 conditions: - "E3 requires rapp-postgres ADR-0003 applied to audit_core and its EXPLAIN probe." - "E4 is unreachable at P1 with one runtime credential." evidence: - "audit_core/ingestion.py" - "tests/test_ingestion.py" R: available: 2 maximum: 2 conditions: - "R4 is unreachable while the integrity chain commits to cleartext hashes." - >- The 30-day horizon is declared but NOT solely ours to hold. audit-core is at P1, and on shared substrate the erasure horizon is the instance maximum across co-residents (§4.5), not the value this consumer declares. A co-resident on platform-pg declaring a longer window extends what a sender's audit records remain recoverable for, without any change here and without notice to the sender. Decision 4.5.4 names this as why a bare R2 is insufficient for a retention promise; we are not a tier, but we are a provider quoting a number we do not control alone, and a sender relying on 30 days should read it as a floor on our side and a platform-derived maximum in practice. - >- A sender needing a horizon it can actually rely on needs audit-core at P2. That is the §4.5 retention trigger and it is recorded in gap.retention_placement with a 2026-12-31 review. evidence: - "rapp-postgres/consumers/audit-core.yaml" - "audit_core/interface.py" V: available: 0 maximum: 1 conditions: - "Exercise restart recovery across audit-core, platform-pg and OpenBao." evidence: A2: - "audit_core/ingestion.py" - "tests/test_ingestion.py" E1: - "audit_core/postgres_backend.py" - "tests/test_backend_conformance.py" P1: "rapp-postgres/docs/evidence/isolation-2026-08-10.md" R2: - "rapp-postgres/consumers/audit-core.yaml" - "tests/test_interface.py"