Boundary
Evidence before state
Observed
Incomplete case recorded as submitted
Not established
A positive decision or downstream access

The screen said done. The record disagreed.

A controlled case reached the ordinary completion screen without all of its required supporting artifacts. On its own, that could have been a display bug: the interface saying "done" while the server still considered the case incomplete.

The server responses said submitted as well. That is the comparison the source notes recorded: the interface and stored status agreed, but the required evidence was missing. The application had advanced the case without enforcing its submission prerequisites.

The distinction from approval matters. Nothing here establishes a favorable review decision, access to a downstream service, or an effect on somebody else's account. The invalid submission state is the finding. Calling it a successful verification would overstate the result.

The HTTP notebook

A read-only status check is where the inconsistency becomes easy to explain. This compact reconstruction puts the conflicting facts next to one another. The JSON names are invented editorial labels, not fields from the original system.

Observation sketch / HTTP/1.1SYNTHETIC + REDACTED

Read-only request

GET /redacted/review-status HTTP/1.1
Host: research.example
Cookie: session=[REDACTED]
Accept: application/json

Illustrative status summary

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: private, no-store
Content-Length: 91

{"submission":"recorded","required_evidence":"missing","review_decision":"not_established"}

Reconstructed for this article, not an original wire capture. The host uses the reserved .example namespace; paths, headers, and data are invented or redacted. Display only: no requests are sent.

The response summarizes the documented inconsistency; it is not a verbatim capture. Requests that advanced the workflow are omitted. Nothing here establishes a positive review decision.

What the next system would believe

A review queue or notification worker may read "submitted" as confirmation that required capture has finished. A partner integration might make the same assumption. These are possible consumers to check during a repair review, not downstream effects established by the notes.

For the status to be trustworthy, every required artifact must exist, belong to this case, be finalized, and satisfy the configured capture rules. An upload can have succeeded while processing is still pending. A valid artifact reference can point to the wrong case. Neither should count as completed evidence.

Keep ready for review separate from review passed. Completing capture is not a favorable decision. Approval needs its own authorized process, inputs, and audit trail.

Check the stored evidence

The client should request submission. The server should decide whether it is allowed, using a server-owned workflow version and evidence from the authoritative store. A client's claim that a step is complete is not evidence of completion.

Evaluate the final transition against one consistent snapshot and record its evidence revision with the submission. Also decide what happens if an artifact is later removed, replaced, or invalidated. Without that rule, a valid submission can become inconsistent after the fact.

Commit the submission and its event record together. A transactional outbox is one way to do that: the transaction stores both, then a worker publishes the event. Consumers still need to handle repeated delivery without repeating the logical action. Publishing first risks announcing a submission that never commits.

The proposed submission gate

This illustrative sketch describes the proposed control, not the affected code. All names are invented. In a real implementation, the lock, evidence reads, state update, and outbox insertion need matching consistency guarantees.

function submit_for_review(actor, case_id, expected_revision):
    transaction:
        case = load_case_for_update(case_id)
        require permitted(actor, "submit", case)
        require case.revision == expected_revision
        require case.state == "collecting"
        policy = load_server_owned_policy(case.policy_version)
        evidence = load_finalized_evidence(case.id)
        require every_requirement_satisfied(policy, evidence)
        require every_item_belongs_to(case.id, evidence)
        save_submission(case, evidence.revision)
        outbox.record_once(case.id, "ready_for_review")
    return submission_receipt(case.id)

# A review decision is made by a separate authorized process.
# This operation does not set a positive decision.

Define retries before shipping this. A repeated completed request should return its existing receipt or a documented conflict, not create another submission. A stale request must not silently submit a newer evidence set.

Test the state and the event

Use local service fixtures, a test database, and a stub for event delivery. These five checks describe the proposed repair, not a remote testing sequence. For every rejection, compare the case, evidence links, and outbox before and after.

Evidence-gate regression cases using synthetic fixtures
CaseFixture and actionRequired assertion
W01 / Missing evidenceCreate a local case with one required artifact absent. Call the submission service as its authorized owner.Validation fails. State, submission records, and outbound events remain unchanged.
W02 / Invalid attachmentSupply a fixture with a foreign-owned or still-processing artifact in the evidence set.Neither variant satisfies the requirement. No submission receipt is created.
W03 / Valid captureFinalize all required local artifacts and submit their current revision.Exactly one submission records that revision. The review decision remains unset.
W04 / Duplicate deliveryRepeat the completed service operation in the test harness.The documented retry result is returned. No duplicate submission or logical notification appears.
W05 / Stale revisionUse a deterministic fixture that changes evidence between preparation and commit.The stale transition conflicts or reevaluates safely. It cannot certify an obsolete evidence snapshot.

Look behind the status flag

A repaired submission gate won't explain older records. An authorized internal consistency check can flag submitted cases that lack their required evidence revision. Review them before drawing conclusions: retention rules, failed processing, or migrations can also account for missing artifacts.

Log case references, policy versions, rejection reasons, and event identifiers where needed. Keep supporting files and personal data out of routine logs. Separate failed prerequisites from ordinary retries so a dropped connection doesn't look like an integrity incident.

Two statuses, two checks

Submission should mean the server checked the required evidence. Approval should mean an authorized review reached a decision. The recorded finding showed the first rule failing; it did not show the second being bypassed. Keeping those claims separate makes both the report and the repair more useful.

Related case: The draft you could change but could not read looks at a different integrity boundary: ensuring that asynchronous writes preserve the authorization checked at the start.

Can your application skip a step that matters?

Our SaaS penetration testing covers business logic, authorization, and critical workflows within the agreed scope. Tell us which process your customers rely on and when you need it tested.

Request a pentest

All case studies | Practical guides