Skip to content

[#3254] pin-advance batch: masfeat/OJK additive extension - #205

Merged
saurabhjain1592 merged 2 commits into
mainfrom
feat/3254-batch2-masfeat-ojk
Aug 4, 2026
Merged

[#3254] pin-advance batch: masfeat/OJK additive extension#205
saurabhjain1592 merged 2 commits into
mainfrom
feat/3254-batch2-masfeat-ojk

Conversation

@saurabhjain1592

@saurabhjain1592 saurabhjain1592 commented Aug 4, 2026

Copy link
Copy Markdown
Member

What

#3254 pin-advance batch (operator ruling, comment 5175919799): the additive interim extends to the masfeat/OJK models. Each of the four named models was verified field-by-field against the server source at the pinned revision (platform/orchestrator/masfeat/types.go and platform/orchestrator/ojk/types.go, community v9.13.0 tag df027c788) - verdicts are this SDK's own, not inherited from go. Key structural fact: the Java masfeat models are populated by hand-written JsonNode parsers in AxonFlow.MASFEATNamespace, so the fiction lives in what the PARSER reads, and the tests drive that parse path, not the post-parse object shape.

Per-model verdicts (with citations)

RegistrySummary - FICTION, plus two real parse bugs

Server serves org_id, total_systems, active_systems, high_materiality, medium_materiality, low_materiality, assessments_due, kill_switches_triggered (types.go:431-440; masfeat-api.yaml matches exactly).

  • Bug: the parser read medium_materiality_count and low_materiality_count ONLY - never-served spellings - so both counters were always 0 against a real server. high_materiality worked only via a second-chance fallback. Fixed: real key first, legacy _count spelling kept as fallback; a PRESENT real key wins even when 0 (intWithFallback).
  • Fiction: by_use_case / by_status have never been in the server struct. Deprecated in place (getters + setters, canonical wording), allowlisted with #3254 notes.
  • Missing real fields, added: orgId (org_id), assessmentsDue (assessments_due), killSwitchesTriggered (kill_switches_triggered).
  • The *MaterialityCount JAVA names are historic (rename forbidden); post-fix they carry real data - documented in the class Javadoc, NOT deprecated.

AISystemRegistry - FICTION

Server serves owner_email + owner_team; technical_owner has never existed (types.go:172-198).

  • Fiction: technicalOwner accessors deprecated (canonical wording). Parser still reads the key for tolerance; it is always null against a real server.
  • Added: ownerEmail (owner_email) - the real key under its true name. businessOwner remains a POPULATED compatibility alias of owner_email (its data is real; documented, pointed at getOwnerEmail(), not deprecated - the canonical names only technical_owner as fiction here).
  • Parser now reads materiality_classification (real) before the legacy materiality spelling.
  • customerImpact/modelComplexity/humanReliance are historic JAVA names reading the real risk_rating_impact/risk_rating_complexity/risk_rating_reliance keys - not fiction, data flows; documented.

KillSwitch - minor fiction, otherwise CLEAN

Server serves trigger_reason (types.go:288); triggered_reason has never been sent.

  • The parser preferred triggered_reason; it worked only via fallback. Fixed: trigger_reason first. The triggeredReason JAVA name is historic and carries real data - no deprecation warranted, no public-surface change (javap diff for KillSwitch: empty).
  • Everything else the model carries maps to real keys (verified against types.go:283-303). Informational gaps, not modeled: restore_reason, trigger_conditions - reported by the gate on every run.

OJKAuditExportResponse (and OJKBreachNotification) - NOT MODELED by this SDK

Zero references under src/, examples/, runtime-e2e/ (grep -rniE "ojk|audit.?export|breach" - the only hit, types/codegovernance/ExportResponse, is the unrelated code-governance export). Nothing to fix, deprecate, or bind. No work invented.

Wire-mapping change

All three modeled classes now carry @JsonProperty tags with the REAL wire names, so the Jackson surface tells the truth and Gate 5 can bind them to the pinned masfeat-api.yaml schemas (which match types.go exactly). The hand-written parsers remain the IO path; the pre-existing MASFEATClientTest suite (fiction-shaped stubs) now exercises the legacy-fallback path and still passes.

Constructor compatibility

These are mutable default-constructor POJOs - no creator constructors exist, so there is nothing to overload and no @JsonCreator ambiguity to manage (the #204 discipline applies trivially). javap exported-surface diff old vs new: additions only - RegistrySummary +6 accessors, AISystemRegistry +2, KillSwitch none. No - lines.

Gate 5 extension + RED-FIRST evidence

AUDIT_BINDING_TYPES is now a registry of schema name to binary class name + source file, supporting nested classes (MASFEATTypes$RegistrySummary) in the probe invocation AND the freshness guard (confirmed: the guard derives the .class path from the binary name and each masfeat entry registers MASFEATTypes.java as its source, so staleness on the new types fails exactly like the audit types).

RED-FIRST (shipped gate run against the PRE-FIX state: models AND allowlist from main, compiled), verbatim - the full camelCase fiction surface of each type is flagged:

  RegistrySummary: (...) with NO backing property in the pinned RegistrySummary schema: ['activeSystems', 'byStatus', 'byUseCase', 'highMaterialityCount', 'lowMaterialityCount', 'mediumMaterialityCount', 'totalSystems'] ...
  KillSwitch: (...) ['accuracyThreshold', 'autoTriggerEnabled', 'biasThreshold', 'createdAt', 'errorRateThreshold', 'orgId', 'restoredAt', 'restoredBy', 'systemId', 'triggeredAt', 'triggeredBy', 'triggeredReason', 'updatedAt'] ...
  AISystemRegistry: (...) ['businessOwner', 'createdAt', 'createdBy', 'customerImpact', 'humanReliance', 'modelComplexity', 'orgId', 'ownerTeam', 'systemId', 'systemName', 'technicalOwner', 'updatedAt', 'useCase'] ...
exit code: 1

(Evidence correction, per review: an earlier revision of this body pasted a 15-key AISystemRegistry list including materialityClassification and metadata - hand-assembled from a discovery iteration, not producible by the shipped gate; materiality_classification was already annotated on main and both names are in the pinned schema. The excerpt above is reshot verbatim from the shipped gate; the reviewer's 13-key reproduction matches it exactly.)

Decoy self-test re-run on the extended surface: a temporary @JsonProperty("batch2_decoy_never_on_wire") field on RegistrySummary - caught by Gate 3 AND Gate 5, exit 1; removed; restored run green.

Deprecation tie (new, batch R3 optional item - implemented). The probe now reports which wire keys are backed by an @Deprecated member (field, getter, setter, or creator parameter); the gate fails any allowlisted key that is genuinely unbound but has no @Deprecated backing member, so named debt stays visible to consumers. Exemption: an allowlist note containing "alias" declares a parser-populated compatibility alias carrying real data (AISystemRegistry.businessOwner). Self-tested both directions: stripping @Deprecated from getByUseCase/setByUseCase goes RED (allowlisted fiction key(s) ['by_use_case'] have no @Deprecated backing member in the model), restored run green. All audit-surface allowlist entries from #204 already satisfy the tie.

Stated limit - registration is manual. A model class is under Gate 5 ONLY if it is listed in AUDIT_BINDING_TYPES; an unregistered model class is invisible to this gate, and Gate 3 catches its drift only when the class name happens to match a spec schema name (and only baseline-aware). Stated in the validate.py docstring. When adding a wire model, register it.

Post-fix GREEN with 4 curated allowlist entries naming #3254: RegistrySummary.by_use_case/by_status (fiction, deprecated in-model), AISystemRegistry.technical_owner (fiction, deprecated in-model), AISystemRegistry.businessOwner (not a wire key: parser-populated compatibility alias of owner_email).

Gate 3 side effect - recorded, not hidden

MASFEATTypes.java was INVISIBLE to the regex discovery on main: its only annotation was fully qualified (@com.fasterxml.jackson.annotation.JsonProperty), so the literal @JsonProperty substring never appeared and discover_sdk_types() skipped the file entirely. The new import + short annotations make the whole file visible, widening Gate 3 coverage from 97 to 108 class/schema pairs and exposing pre-existing camelCase drift in 8 out-of-scope masfeat request/response types (FEATAssessment, ConfigureKillSwitchRequest, CreateAssessmentRequest, Finding, UpdateAssessmentRequest, RejectAssessmentRequest, RestoreKillSwitchRequest, TriggerKillSwitchRequest). That drift is now RECORDED in per_type_drift at the current pin - the #3254 pin-advance burn-down owns removing it - rather than staying structurally invisible. (These request types never hit the wire through Jackson: the client builds their request bodies by hand, e.g. registerSystem maps customerImpact to risk_rating_impact explicitly.)

Test evidence

  • mvn verify green: 1342 unit tests (6 new in MASFEATRealWireTest) + 12 integration, jacoco met.
  • MASFEATRealWireTest drives the REAL parse path (public client methods over WireMock) with SOURCE-DERIVED payloads (declared as such - masfeat is enterprise-gated, no community capture possible). Mutation-proof discriminators that fail against the pre-fix parser: real-key-wins for medium/low counters (pre-fix: 0), for high_materiality even at value 0 (pre-fix: fallback 9), for trigger_reason (pre-fix: "legacy-fiction"), for materiality_classification (pre-fix: LOW). Plus fiction-null assertions, legacy-fallback tolerance, and absence tolerance.
  • Live leg (real community v9.13.0 stack, per the canonical "attempt once, report honestly"):
PASS [community-gate] masfeat route refused cleanly by a community stack (enterprise-only module, no routes registered): AxonFlowException: 404 page not found
NOTE: enterprise leg NOT exercised on this stack - real-field assertions rest on the source-derived WireMock suite (src/test/java/com/getaxonflow/sdk/masfeat/MASFEATRealWireTest.java).

The new runtime-e2e/masfeat_registry_summary/ suite asserts either leg (enterprise: real fields readable + fiction maps null; community: clean HTTP refusal, specifically NOT a parse error) and prints which one ran.

Items not modified in this PR (with justification)

  • OJKAuditExportResponse / OJKBreachNotification: not modeled by this SDK (proof above) - the canonical's OJK item has no Java surface to fix.
  • KillSwitch restore_reason / trigger_conditions and AISystemRegistry data_sources/model_type/version/deployment_date/last_assessment_date/next_assessment_due/updated_by: real spec fields not modeled - the canonical scopes this batch to fiction-class wrongness, not full coverage; Gate 5 reports them as informational on every run.
  • The 8 out-of-scope masfeat request/response types' camelCase drift: recorded in the baseline at the current pin (see Gate 3 section) for the #3254 burn-down; fixing them here would balloon the batch beyond the ruling.
  • MASFEATClientTest's fiction-shaped stub payloads: retained deliberately - they now pin the legacy-fallback tolerance path.
  • Java accessor names (getTriggeredReason, getHighMaterialityCount, getCustomerImpact, ...): renames are forbidden by the additive rule; each carries a wire-mapping note in Javadoc instead.

…, deprecations (#3254)

Per-model verdicts against platform/orchestrator/masfeat/types.go at
community v9.13.0 (df027c788), verified field-by-field, not inherited
from the go SDK (the Java models are populated by hand-written JsonNode
parsers in AxonFlow.MASFEATNamespace, so the fiction lives in what the
PARSER reads):

- RegistrySummary: FICTION + parse bugs. The parser read
  medium_materiality_count / low_materiality_count ONLY - the server
  serves medium_materiality / low_materiality (types.go:436-437), so
  both counters were ALWAYS 0 against a real server. high_materiality
  worked only via a second-chance fallback. by_use_case / by_status
  have never been served (types.go:431-440) - deprecated in place.
  org_id, assessments_due, kill_switches_triggered were not modeled -
  added. Parser now reads real keys first with legacy-spelling
  fallback (a PRESENT real key wins even when 0).
- AISystemRegistry: FICTION. technical_owner has never been served
  (types.go:172-198) - accessors deprecated. owner_email is the real
  key: new ownerEmail field carries it under its true name;
  businessOwner remains a populated compatibility alias (documented,
  not deprecated - its data is real). materiality_classification (real)
  now read before the legacy 'materiality' spelling. The
  customerImpact/modelComplexity/humanReliance java names are historic
  but read the real risk_rating_* keys - not fiction, documented.
- KillSwitch: minor fiction. The parser preferred triggered_reason,
  which the server has never sent - trigger_reason (types.go:288) is
  now read first, legacy spelling kept as fallback. No public surface
  change. restore_reason / trigger_conditions remain unmodeled -
  informational gap, reported by the gate on every run.
- OJKAuditExportResponse (and OJKBreachNotification): NOT MODELED by
  this SDK - zero references under src/, examples/, runtime-e2e/.
  Nothing to fix or bind.

All three modeled classes now carry @JsonProperty tags with the REAL
wire names so the Jackson surface tells the truth and Gate 5 can bind
them to the pinned masfeat-api.yaml schemas (which match types.go
exactly). Constructor compatibility: these are default-constructor
POJOs - no creator constructors exist, so nothing to overload; the
javap surface diff is additions-only (KillSwitch: none).

Gate 5 extended: AUDIT_BINDING_TYPES is now a registry of schema name
-> binary class name + source file, supporting nested classes
(Outer$Inner) in both the probe invocation and the freshness guard.
RED-FIRST evidence (extended gate, pre-fix models from main, verbatim
in the PR body): all three types flagged with their full camelCase
fiction surface. Decoy self-test re-run on the extended surface
(caught, removed). Post-fix green with 4 curated allowlist entries
naming #3254 (by_use_case, by_status, technical_owner, businessOwner).

Gate 3 side effect, recorded not hidden: MASFEATTypes.java was
INVISIBLE to the regex discovery on main (its only annotation was
fully qualified, so the literal '@JsonProperty' substring never
appeared and discovery skipped the file). The new imports make the
whole file visible, which widens Gate 3 coverage from 97 to 108
class/schema pairs and exposes pre-existing camelCase drift in 8
out-of-scope masfeat request/response types (FEATAssessment,
ConfigureKillSwitchRequest, ...). That drift is now RECORDED in
per_type_drift at the current pin - the #3254 burn-down owns removing
it - instead of staying structurally invisible.

Tests: source-derived payloads (declared as such - masfeat is
enterprise-gated, no community capture possible) driving the REAL
parse path through the public client over WireMock, with
both-spellings discriminators that fail against the pre-fix parser
(real-key-wins for materiality/trigger_reason/medium/low counters),
plus absence tolerance. Runtime-e2e leg
runtime-e2e/masfeat_registry_summary/ run against the live community
stack: community-gate outcome exercised (clean 404 refusal, not a
parse error); enterprise leg honestly reported as not exercised.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
- Deprecation tie (optional item, implemented): the probe now reports,
  per bound type, which wire keys are backed by an @deprecated member
  (field, getter, setter, or creator parameter - runtime retention).
  validate.py fails any allowlisted key that is genuinely unbound but
  has NO @deprecated backing member, so named debt stays visible to
  consumers. Exemption: allowlist notes containing 'alias' declare a
  parser-populated compatibility alias carrying real data (the
  AISystemRegistry.businessOwner case); documented in the allowlist
  _comment and the validate.py docstring. Self-tested both directions:
  stripping @deprecated from getByUseCase/setByUseCase goes RED naming
  by_use_case; restored run green.
- Manual-registration caveat stated in the validate.py docstring: a
  model class is under Gate 5 ONLY if listed in AUDIT_BINDING_TYPES;
  an unregistered class is invisible to this gate, and Gate 3 catches
  its drift only when the class name matches a spec schema (and only
  baseline-aware).
- PR body evidence hygiene: the RED-FIRST AISystemRegistry excerpt was
  reshot from the shipped gate against main-compiled classes WITH
  main's allowlist (the faithful pre-fix state): 13 keys, including
  businessOwner, excluding materiality_classification and metadata
  (both real and bound on main). The previous paste was hand-assembled
  from an earlier discovery iteration - corrected in the PR body.

Signed-off-by: Saurabh Jain <saurabh.jain@getaxonflow.com>
@saurabhjain1592
saurabhjain1592 merged commit 5540825 into main Aug 4, 2026
18 checks passed
@saurabhjain1592
saurabhjain1592 deleted the feat/3254-batch2-masfeat-ojk branch August 4, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant