Skip to content

feat(analysis): bind membership-target refusals to an analysis-run profile - #434

Closed
seonghobae wants to merge 3 commits into
mainfrom
feat/membership-target-analysis-run-gap-004
Closed

feat(analysis): bind membership-target refusals to an analysis-run profile#434
seonghobae wants to merge 3 commits into
mainfrom
feat/membership-target-analysis-run-gap-004

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Consolidation status

fold_into_landing_vehicle — this PR binds one existing membership_target refusal family into an Analysis Run profile. That is implementation evidence inside the Validation / Analysis Run boundary, not an independently shippable bounded context. Preserve its typed target distinctions, digest-bound census, execution/artifact bounds, metric-free inspect boundary, duplicate/profile/snapshot/cutoff refusals, and focused tests, but do not merge it independently while queue-authority recovery is active. Fold these mechanics with compatible refusal profiles into the coherent Validation / Analysis Run landing vehicle selected under #435. ADR 0069 is implementation evidence pending #437 normalization, not per-rule architecture authority.

Summary

GAP-004 leftover / ADR 0069. Bind existing membership_target::MembershipTargetKind and refuse_collapsed_target to a cutoff-safe membership_target_v1 analysis-run profile (tepp.membership_target.v1).

  • Mixed language/episode/template/department/opportunity-pool/entity/project corpora emit a digest-bound census with matching refusal counts.
  • Inference status language_episode_template_department_opportunity_pool_are_not_entities.
  • identity_recovery_rate stays library-side; inspect payloads stay metric-free (scientific_acceptance never appears).
  • Empty, typed-only, persistence-only, single-kind, duplicate identities, and snapshot/profile/cutoff mismatch fail closed.
  • MAX_EVIDENCE_UNITS now applies both to execution input and to deserialized/serialized artifact counts, so a compact forged JSON artifact cannot claim an impossible corpus size.
  • Distinct from location-membership, membership-posterior ICC, copied-text, copy-identity, outcome-order, relation-absence, and episode-membership rules; distinct does not imply separate bounded-context ownership.

Not GPU. Not MCMC. Not topic birth/split/merge. Not implemented-main.

RED -> GREEN repair evidence

  • RED d3fa7276d4e0f0382d2c65e5404438ef26bdf67a: requires an internally consistent compact artifact with document_count > MAX_EVIDENCE_UNITS to fail both serialization validation and deserialization validation, and requires an execution corpus of MAX_EVIDENCE_UNITS + 1 documents to fail LimitExceeded before census work.
  • GREEN a976c99255073c5fe08b4d850833e068fca11110: imports the shared bound into this profile, rejects oversized execution slices before allocation/census work, and enforces the same bound in MembershipTargetArtifact::validate().

Verification

Fresh exact-head hosted verification is required on a976c99255073c5fe08b4d850833e068fca11110; queued/pending/predecessor evidence is non-passing. Folded landing-vehicle verification must preserve these tests.

Merge gate

Do not merge this micro-profile independently during queue recovery. Preserve its unique tests/evidence in the folded landing vehicle, then satisfy that exact head's active ruleset, required workflows, review-thread resolution, and current review policy.

…ofile

GAP-004 leftover / ADR 0069. Bind existing MembershipTargetKind and
refuse_collapsed_target to cutoff-safe membership_target_v1. Language,
episode, template, department, and opportunity-pool targets are not
entities; identity_recovery_rate stays library-side.
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator-visible GAP-004 leftover / ADR 0069. Binds existing membership_target::MembershipTargetKind + refuse_collapsed_target to membership_target_v1 (tepp.membership_target.v1). Inference language_episode_template_department_opportunity_pool_are_not_entities. Distinct from #430 location-membership, #398 membership-posterior ICC, #427 copied-text, #416 copy-identity. identity_recovery_rate stays library-side. Two independent current-head APPROVEs required. Author/bot COMMENTED is not APPROVE. Exact-head Checks on c0fbaab only. Do not self-approve.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Devin Review

Comment on lines +27 to +29
document_id: String,
kind: MembershipTargetKind,
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Historical censuses admit future evidence

Any MembershipTargetDocument is counted without an availability timestamp or cutoff check. Future-available documents can contaminate historical results.

Prompt for agents
Add availability provenance to MembershipTargetDocument and enforce cutoff eligibility inside execute_membership_target_run before counting. The executor must not trust an unbound KnowledgeCutoff argument as proof that every supplied document was available. Bind each document to an AvailableTime or a validated snapshot type, exclude or fail on documents later than the request cutoff, and add tests containing both eligible and future-available documents.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +222 to +224
if !seen.insert(document.document_id()) {
return Err(AnalysisEngineError::DuplicateEvidence);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Multiple memberships fail as duplicates

When one document has several memberships, seen.insert rejects its second target as duplicate. Valid multiple-membership analyses fail.

Prompt for agents
Represent multiple target assignments for one document without treating them as duplicate evidence. In execute_membership_target_run, distinguish duplicate rows from distinct target kinds for the same document, likely by keying duplicate detection on (document_id, kind) or by modeling one document with a set of kinds. Define whether document_count counts unique documents or assignments, update per-kind and refusal invariants accordingly, and test one document assigned simultaneously to language, template, opportunity pool, episode, entity, and project targets.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +211 to +221
let mut seen = std::collections::BTreeSet::new();
let mut language_count = 0_u64;
let mut episode_count = 0_u64;
let mut template_count = 0_u64;
let mut department_count = 0_u64;
let mut opportunity_pool_count = 0_u64;
let mut entity_count = 0_u64;
let mut project_count = 0_u64;
let mut refused_as_entity_count = 0_u64;
let mut refused_as_project_count = 0_u64;
for document in documents {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Membership runs bypass corpus limits

execute_membership_target_run scans every supplied document without enforcing the engine’s 100,000-unit bound. Oversized runs consume unbounded time and memory.

Prompt for agents
Enforce the analysis engine's MAX_EVIDENCE_UNITS bound before allocating the duplicate-detection set or scanning documents in execute_membership_target_run. Return AnalysisEngineError::LimitExceeded for oversized input and add a boundary test for MAX_EVIDENCE_UNITS + 1.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +314 to +319
let summary = AnalysisResultSummary::new(
"membership_target",
document_count,
4,
MEMBERSHIP_TARGET_INFERENCE_STATUS,
)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Statistic count lacks defined meaning

The summary reports four statistics without mapping them to artifact fields. Define the count’s semantics so consumers can interpret it consistently.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@cwl-noema-review cwl-noema-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noema LLM review

The PR adds a membership-target analysis-run profile, but it does not address the three blocking issues raised in prior review threads: (1) MembershipTargetDocument carries no availability timestamp, so the executor counts future-available documents as historical evidence; (2) duplicate detection keys on document_id alone, so a single document with multiple membership kinds is rejected as DuplicateEvidence; (3) execute_membership_target_run never enforces MAX_EVIDENCE_UNITS, allowing unbounded scans. These are confirmed by source inspection and the existing tests. The artifact validation and refusal logic are otherwise sound, but the executor's core invariants are incomplete.

Reviewed changed lines

  • crates/analysis_engine/src/membership_target_artifact.rs:29 (RIGHT): MembershipTargetDocument contains only document_id and kind. There is no availability timestamp or cutoff-eligibility field. The executor accepts a KnowledgeCutoff argument but never checks per-document availability, so any document supplied is counted regardless of whether it was available at the cutoff. This violates the 'cutoff-safe' claim and the ADR 0022 contract.
  • crates/analysis_engine/src/membership_target_artifact.rs:224 (RIGHT): The duplicate-detection set seen is keyed on document.document_id() only. If one document has multiple membership kinds (e.g., language and entity), the second row is rejected as DuplicateEvidence. The ADR and changelog claim multiple-membership aggregation, but the implementation collapses them. The test empty_single_class_and_duplicate_identities_fail_closed explicitly expects DuplicateEvidence for the same id with different kinds, confirming the behavior.
  • crates/analysis_engine/src/membership_target_artifact.rs:221 (RIGHT): The executor iterates over documents without any length check against MAX_EVIDENCE_UNITS (100,000). The engine's global bound is defined in lib.rs but not enforced here. An oversized input will allocate a BTreeSet of unbounded size and scan all rows, causing unbounded time and memory. The prior review thread flagged this and the code does not address it.
  • crates/analysis_engine/src/membership_target_artifact.rs:319 (RIGHT): The summary uses a hardcoded statistic count of 4, but the artifact exposes 11 count fields (document_count, language_count, episode_count, template_count, department_count, opportunity_pool_count, entity_count, project_count, refused_as_entity_count, refused_as_project_count, plus inference_status). The meaning of '4' is undefined and inconsistent with the artifact schema. This is a maintainability/contract issue.

Adversarial validation

  • crates/analysis_engine/src/membership_target_artifact.rs:29 (RIGHT) confirmed: A document with no availability timestamp is counted even if it was not available at the knowledge cutoff. — Source: MembershipTargetDocument has only document_id and kind; execute_membership_target_run takes knowledge_cutoff but never compares it to any per-document time. The loop counts every document unconditionally.
  • crates/analysis_engine/src/membership_target_artifact.rs:224 (RIGHT) confirmed: A single document with two different membership kinds is rejected as duplicate. — The integration test empty_single_class_and_duplicate_identities_fail_closed constructs exactly this case and asserts Err(DuplicateEvidence). The code path is confirmed.
  • crates/analysis_engine/src/membership_target_artifact.rs:221 (RIGHT) confirmed: An input larger than MAX_EVIDENCE_UNITS is not rejected and causes unbounded work. — Source: no if documents.len() > MAX_EVIDENCE_UNITS check exists in execute_membership_target_run. The constant is defined in lib.rs but unused here. The loop runs unconditionally.
  • Residual risk: The three blocking issues are confirmed by source inspection. The executor cannot be trusted to produce cutoff-safe, multi-membership, bounded results until they are fixed.

Findings

  • [high] crates/analysis_engine/src/membership_target_artifact.rs:29 (RIGHT): MembershipTargetDocument lacks availability provenance. The executor counts every supplied document without checking whether it was available at the knowledge cutoff, allowing future-available documents to contaminate historical results. Add an AvailableTime or validated snapshot timestamp to each document and enforce cutoff eligibility before counting.

  • [high] crates/analysis_engine/src/membership_target_artifact.rs:224 (RIGHT): Duplicate detection keys on document_id alone, so a single document with multiple membership kinds is rejected as DuplicateEvidence. Key on (document_id, kind) or model one document with a set of kinds, and define whether document_count counts unique documents or assignments.

  • [high] crates/analysis_engine/src/membership_target_artifact.rs:221 (RIGHT): execute_membership_target_run does not enforce MAX_EVIDENCE_UNITS. Oversized inputs cause unbounded time and memory. Add a length check before allocating the duplicate-detection set or scanning documents, returning AnalysisEngineError::LimitExceeded.

  • [low] crates/analysis_engine/src/membership_target_artifact.rs:319 (RIGHT): The summary statistic count of 4 is hardcoded and does not correspond to the artifact's 11 count fields. Define the semantics of the statistic count or derive it from the artifact schema to avoid consumer confusion.

  • Result: REQUEST_CHANGES

  • Head SHA: c0fbaabd8c95e69407c3b9e50f8d1846bd949598

  • Reviewer credential: noema-review-github-app

  • Actor: cwl-noema-review[bot]

Copy link
Copy Markdown
Contributor Author

Hour-35 exact-head review request.

Current head c0fbaabd8c95e69407c3b9e50f8d1846bd949598. ADR 0069 membership_target_v1. Author COMMENT 5073678769 is not APPROVE. cwl-noema-review[bot] CHANGES_REQUESTED is not independent APPROVE and does not authorize weakening fail-closed duplicate-identity or mixed-census gates. Exact-head Checks on c0fbaab only. Predecessor Checks do not transfer. Do not duplicate membership-target. Do not self-approve.

@opencode-agent review

Copy link
Copy Markdown
Contributor Author

Hour-36 re-verify: membership-target head c0fbaab. noema CHANGES_REQUESTED is not independent APPROVE; do not weaken fail-closed to satisfy it. Distinct from this hour's #458 outcome-order (AvailableTime + MAX_EVIDENCE_UNITS already on that slice). Zero independent APPROVEs. Exact-head Checks on c0fbaab only. Do not self-approve. @opencode-agent review.

seonghobae added a commit that referenced this pull request Sep 2, 2026
Preserve #416 copy-identity, inferred-status, location-membership,
episode-membership, and subevent-containment source/tests with #434
membership-target profile evidence. Shared Cargo/lib/lock/docs surfaces
are unioned; neither side is discarded. Per-profile ADR numbers remain
implementation lineage pending #437.

# Conflicts:
#	CHANGELOG.md
#	DOCUMENTATION.md
#	crates/analysis_engine/Cargo.toml
#	crates/analysis_engine/src/lib.rs
#	docs/TRACEABILITY.md
#	docs/adr/README.md

Copy link
Copy Markdown
Contributor Author

superseded_by_fold — unique membership_target_v1 source, tests, ADR 0069, and doctoring are on surviving #416 head b0d6cb8969aa0ddd386ab82a1755155fa13d18a8 via --no-ff merge of the same SHA. Shared Cargo/lib/lock/docs surfaces were unioned; copy-identity, inferred-status, location-membership, episode-membership, and subevent-containment evidence were not discarded. This child is landing-vehicle lineage, not implemented-main. Do not restore it as a direct-main Analysis Run micro-PR.

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