Skip to main content

The export that passed every permission check

· 26 min read
Fault Lines Editorial
Fictional incidents. Exact technical vocabulary.
Architecture storyProfessionalA fictional production incident about composed agent authority, effect-bound approval, and releasing exact bytes to the right person.

Harborlight People is a fictional workforce-management SaaS company. Its employer customers use the platform for payroll, scheduling, employee relations, support, and access administration. They can also configure a workflow through which current and former workers request copies of their personal data.

The employer customer defines the request policy, the source categories in scope, and who may make the final release decision. Harborlight operates the software and a managed privacy-operations team under that customer-defined policy. The process in this story is Harborlight's fictional design, not a universal legal requirement.

Harborlight handled about 3,800 worker-data requests per month. Most looked simple from the request portal: prove who you are, describe the employment period, wait while the records are assembled, and collect a package from an authenticated portal.

The work behind that path was not a single database query.

A worker might have a legal name, a preferred name, a former surname, several email addresses, and more than one worker identifier after a rehire or contractor conversion. A support ticket might mention a person without being about that person. An attachment might be linked to a ticket whose requester and uploader were neither its subject. A fixed join could recover the obvious records. It could not reliably resolve every free-text reference, copied attachment, or derived record.

That was why Harborlight built Lumen, a bounded AI privacy worker. Lumen read case-scoped records through narrow adapters, resolved aliases and free-text references, proposed which records concerned the requester, explained the evidence behind each proposal, and suggested redactions. Lumen could produce candidates. It did not own a person's stable identity, record-subject truth, case policy, approval, delivery credential, or the release of bytes.

Mara Chen, the Senior Privacy Operations Specialist responsible for defining request scope and resolving ambiguous record matches, supervised the managed workflow. Under each employer's policy, she could approve an exact release.

The distinction sounded conservative enough: Lumen proposed; Harborlight decided.

How a verified worker normally receives an export

Loading interactive story context…
Concepts in this story5 concepts
Privilege compositionIdentity and security · Capability containment

The broader capability created when several individually permitted operations can be chained. Local least-privilege checks do not establish that the complete path or resulting effect is authorized.

Source-to-sink authorizationIdentity and security · Information-flow control

An authorization decision over the complete path from protected input through transformations to a consequential destination. It checks whether this data may reach this sink for the current purpose, not only whether each intermediate tool call is allowed.

Record-subject bindingData systems · Provenance and identity

The supported relationship between a versioned record and the person or entity it concerns. A model may propose this relationship from semantic evidence, but release authority requires corroborating source metadata, policy, or explicit review.

Effect-bound approvalAgentic systems · Approval binding

Human authorization tied to one canonical proposed effect: its target, parameters, evidence, constraints, destination, and expiry. A material change creates a new approval question instead of inheriting consent from the surrounding task.

Execution-time authorizationAgentic systems · Effect authorization

Re-evaluating current principal, workload, resource, purpose, policy, approval, and effect immediately before a consequential action. Permission observed during planning or approval is historical evidence, not automatic present authority.

The normal journey crossed four responsibility groups:

  1. A worker authenticated in Harborlight's request portal and opened a case.
  2. The case service bound the employer tenant, requester, configured purpose, approved source categories, employment period, and current case status. Mara reviewed that scope and permitted candidate discovery.
  3. Case-scoped adapters retrieved records and versioned metadata. Lumen proposed record inclusion and redactions from stable identifiers, aliases, references, attachments, and contextual evidence.
  4. Redaction and export services built a candidate package inside an encrypted case staging area.
  5. A delivery service published an approved immutable package version into a protected worker-portal namespace.
  6. The portal reauthorized each read, and the worker received the package or an explicit ambiguity or delay notice.

Source permission answered whether a service could read a tenant, record class, or time range. Lumen's semantic judgment answered whether a record was a plausible candidate. Release authorization was a different question: whether the exact bytes in the package could go to the exact current recipient for this case and purpose.

The effect boundary was the component that made those bytes available outside Harborlight's internal review path.

The design the team trusted

Harborlight's implementation looked like a careful separation of duties.

The request portal and privacy-operations console were React applications. A NestJS service called privacy-case-api stored cases in PostgreSQL. A Python lumen-worker ran in Temporal workflows on Amazon EKS. Case-scoped adapters could read only the approved tenant and source categories in the employer's HR database, payroll service, scheduling service, support index, access logs, and versioned S3 attachments.

A controlled PostgreSQL identity graph mapped typed, versioned identifiers across those systems. It treated primary_subject, mentioned_person, requester, author, approver, and manager_of_subject as different relationships instead of collapsing them into one person_id.

Separate containers ran the redaction service and export-builder. Candidate packages went into encrypted S3 prefixes isolated by case. A notification service could contact only the verified address or portal subject on that case. OpenTelemetry traces and an append-only event store recorded the workflow.

Each component enforced a narrow policy:

  • Search could read only the current employer tenant, the approved source categories, and the approved employment period.
  • Lumen received case-scoped source access, never a general publication or portal credential.
  • Redaction ran under a versioned policy.
  • export-builder could write only inside the requester's isolated case prefix.
  • Notification could contact only the verified recipient bound to the case.
  • The portal authenticated the recipient and checked access to the case package.

The initial human approval covered the purpose and discovery scope: which tenant, sources, and period the workflow could inspect. Lumen's selected candidate set then flowed into redaction and packaging as if selection had also established eligibility for release.

The package record stored the case and recipient. It did not store an authoritative record-subject decision for every included source. The delivery service checked the case and recipient before publication, but it did not ask which evidence allowed every source record's bytes to reach that recipient.

The team's mental model was reasonable:

Source read, package staging, and protected publication are each least-privileged. The workflow assembled from them is therefore least-privileged.

There was no broad email tool, public object-storage credential, or model-controlled portal session to make the flaw look obvious.

Tuesday at 09:00

At 09:00, Jana Li, a verified former worker and the intended recipient, authenticated, completed Harborlight's fictional identity-verification flow, and opened case PR-2048 for her former employment.

At 09:18, Mara approved candidate discovery across payroll, scheduling, support, and access logs during Jana's employment period. She approved a search scope, not an exact package that did not yet exist.

At 09:31, Lumen loaded Jana's stable HR person ID, former worker ID, legal name, former surname, email history, and known source relationships. It began candidate discovery through the scoped adapters.

At 09:44, an allowed support ticket produced an ambiguous lead. The ticket contained “J. Li” and linked a versioned salary-adjustment attachment. It was in the correct employer tenant, an approved source category, and the relevant period. Its structured requester and attachment uploader did not identify the person the attachment described.

The attachment concerned Jian Li, a different current employee in the same tenant with a different stable person ID and worker ID.

At 09:46, Lumen combined name similarity, topic, date, and organizational context. It proposed that the attachment concerned Jana. That was a plausible semantic mistake, not an invented identity merge and not a bypass of a source control.

At 09:49, the support adapter correctly returned the allowed ticket and attachment. The redaction service removed bank details under its configured rule. It retained Jian's salary and performance explanation because the service assumed that candidate eligibility had already been established upstream.

At 09:55, export-builder correctly wrote the package into Jana's encrypted case prefix. The package stayed inside the correct tenant and case.

At 10:02, the delivery service confirmed that PR-2048 was open and that Jana's portal subject was its recipient. It published the package and sent a notification. Every local policy decision returned allow.

At 10:11, Jana authenticated in the portal and opened the exact package prepared for her case. Alongside her own records, she found Jian's salary-adjustment attachment. The bank details were gone. Jian's salary and performance explanation were not.

Jana reported it.

The package had reached the correct verified person. It contained the wrong person's sensitive employment data.

Incident diagnosis · six local decisions

Six green checks, one unauthorized path

Read downward. Every check is genuinely valid; each card also names the release fact that the check never established.

  1. Local check passed

    Requester identity: allow

    Jana authenticates, completes Harborlight’s fictional verification flow, and opens case PR-2048.

    It proves
    Jana may receive an authorized package.
    It does not prove
    It does not prove every candidate record concerns Jana.
  2. Local check passed

    Case scope: allow

    Mara permits discovery across the employer, sources, purpose, and employment period recorded on the case.

    It proves
    The search territory and purpose are approved.
    It does not prove
    It does not approve an exact future release object.
  3. Local check passed

    Search authorization: allow

    Lumen proposes a salary-adjustment attachment after matching “J. Li,” topic, date, and organization context.

    It proves
    The workflow may read the attachment as a candidate.
    It does not prove
    It does not prove Jian’s bytes may be released to Jana.
  4. Local check passed

    Redaction policy: allow

    The configured transformation removes bank details but retains Jian’s salary and performance explanation.

    It proves
    The configured sensitive fields were removed.
    It does not prove
    It does not prove the remaining record belongs in Jana’s case.
  5. Local check passed

    Export storage: allow

    The export builder writes the candidate package only to Jana’s encrypted case prefix.

    It proves
    The package stayed inside the intended case storage boundary.
    It does not prove
    It does not prove every included byte is authorized.
  6. Local check passed

    Protected publication and access: allow

    At 10:02 the service publishes to Jana’s protected portal namespace; at 10:11 Jana authenticates and the portal serves the package.

    It proves
    The package was bound to Jana’s case, and Jana was authenticated when its bytes were served.
    It does not prove
    It does not prove the package content was safe for Jana.
  7. Unauthorized outcome

    Exact bytes about Jian reach Jana

    The verified recipient receives another worker’s salary and performance explanation. The workflow can show six allows, but no owned relationship that authorized those bytes for Jana.

The missing control: permission to read a candidate never became source-to-sink authorization to release its exact bytes.

The ordered path was all green until the outcome:

  1. Jana's identity was valid. That proved she could receive a package authorized for Jana; it did not prove every candidate concerned her.
  2. The case scope was valid. That proved the tenant, sources, period, and purpose were approved; it did not define an exact release object.
  3. Search authorization was valid. That proved Lumen could read the attachment as a candidate; it did not permit Jian's bytes to go to Jana.
  4. Redaction was locally valid. That proved configured fields were removed; it did not prove the remaining record belonged in Jana's case.
  5. Export storage was valid. That proved the package stayed in Jana's isolated case area; it did not authorize every byte inside it.
  6. Portal authorization was valid. That proved Jana opened the case package; it did not prove that package was safe for Jana.

Six green checks and one unowned relationship

At 10:36, Elias Ward, the Staff Security Engineer responsible for Harborlight's data-release control plane, joined the source, Lumen, redaction, export, and portal logs.

The trace was unusually complete. It proved which workload had called each adapter, which policy version had run, where the package had been written, and which authenticated portal subject had opened it.

It could not answer the question Elias cared about:

Which authoritative evidence permitted bytes derived from Jian's attachment to become available to Jana?

No component owned that relationship.

The first proposed fix was to improve entity resolution. The team could add more identifiers, tune Lumen's prompt and evaluator, raise a confidence threshold, and retrieve metadata before content. All were useful improvements. None repaired the authority boundary.

Suppose Lumen reached 99.9% accuracy on Harborlight's evaluation set. The remaining rare false inclusion would still travel automatically from allowed search, through allowed redaction and allowed case storage, to an allowed recipient. Model accuracy changed how often the system proposed a dangerous package. It did not change who was allowed to release it.

Elias redrew the workflow as a graph of reachable effects instead of a list of tool scopes.

Search alone could reveal records only inside Harborlight's case workflow. export-builder alone could stage only a case package. Protected publication and portal serving alone could expose only the package bound to Jana's case. Composed as read → stage → publish/serve, the three narrow permissions created a broader capability: select protected source bytes and make them available to a person. Notification changed discoverability; because email was only a locator, it was not the content-release permission.

No local credential had widened. The reachable path had.

That made the next control question unavoidable.

Harborlight had authorized each edge locally. It had not authorized the whole path from a protected source record, through transformations and staging, to the exact released bytes and authenticated recipient.

The rule Elias wrote at the top of the incident document was shorter:

Permission to find a record is not permission to release it, and three narrow permissions do not make a narrow path.

A candidate was not a subject decision

The Jana/Jian collision exposed another overloaded word: “match.”

Lumen had matched text and context to Jana's case. It had not changed Harborlight's stable person identifiers. Nor had it established that the attachment's role relative to Jana was primary_subject rather than mentioned_person, requester, or no supported relationship at all.

For structured records, Harborlight could consume typed, versioned relationships from the domain that owned them. Core HR owned its person and worker identifiers. A payroll domain could own an explicit payroll-subject relationship. Object storage owned an attachment's bytes and version, but its folder did not establish who the attachment was about.

For unstructured “aboutness,” no universal automatic table existed. Lumen could supply a candidate relationship, confidence, rationale, conflicts, and stable source reference. Deterministic policy could reject known tenant or person contradictions. Where evidence remained uncertain, the workflow needed an explicit AMBIGUOUS_REVIEW state and Mara needed the canonical metadata, not only Lumen's summary.

Names, email addresses, and similarity scores remained evidence. They did not become stable subject keys because they looked convincing.

Harborlight also stopped reducing eligibility to SUPPORTED or reviewerApproved. A relationship assertion described how evidence connected a record to a person; it did not itself decide what this case and purpose could release. A separate typed ReleaseEligibility recorded the policy rule and version, ALLOW | DENY | UNKNOWN | CONFLICT, reviewer and rationale when needed, and whether the decision covered a whole immutable record, named fields or byte ranges, or one exact transformed output. Multi-subject records could therefore be partly eligible. Generated filenames, cover pages, summaries, archive metadata, previews, and redacted output carried lineage and eligibility too.

Approval had to name the effect

Mara had approved a legitimate case and reasonable discovery scope. Asking her to “review more carefully” would repeat the same defect if the review screen still described a general intent instead of an exact future release.

Harborlight stopped calling several different records one “envelope.” It defined them separately:

  • ReleaseProposalV1 was an immutable body containing schema and canonicalization versions; case, tenant, requester, recipient, purpose, and source scope; exact source and evidence versions; relationship assertions; typed, scoped release-eligibility decisions; transformation and redaction versions; immutable package object version, payload digest, length, media type, and package-format version; destination, channel, and validity constraints. It contained no digest, approver, receipt, nonce, or mutable status.
  • The canonicalizer encoded that body as UTF-8 using RFC 8785 JSON canonicalization plus Harborlight's ReleaseProposalV1 schema. SHA-256 covered those exact canonical bytes. The schema defined omitted and default values; unknown fields, duplicate keys, aliases, non-canonical numbers, and unsupported versions failed closed.
  • A separate ApprovalReceipt named the proposal digest, schema and canonicalization versions, approver and role, approval time, expiry, one-use nonce, and decision. The approval store separately owned current APPROVED, REVOKED, SUPERSEDED, RELEASING, or SPENT state and its revision.
  • A ReleaseReceipt later recorded the operation, proposal and payload digests, immutable object version, gateway workload, task and tenant, employer-policy decision and revision, configured recipient subject, destination, publication event and result, and time. An AccessReceipt recorded the authenticated portal subject, proposal and payload digests, exact object version, served range and byte count, and whether access completed or stopped.

The reviewer UI and gateway deserialized the same canonical proposal bytes. Mara saw the exact package content—or a complete, byte-accounted rendering of it—rather than only Lumen's summary. Her approval attested to the separate proposal digest.

Approval of “process Jana's request” no longer authorized any package the workflow later assembled. It authorized one proposal, one evidence snapshot, one immutable package version, one recipient, one purpose, one redaction implementation, one delivery channel, and one validity window.

Adding or replacing a record was material. So was changing a source version, subject decision, redaction policy, payload, recipient, destination, or delivery channel. A material change moved the case to REAUTHORIZE unless an explicit reviewed policy said that particular change was immaterial.

The digest protected equality after Harborlight defined and canonicalized the fields. It did not prove that a relationship inside the proposal was true. A perfectly hashed mistake was still a mistake.

The proposal, complete review rendering, receipts, and preview also became sensitive derived artifacts. Harborlight had to restrict and monitor access to them, encrypt them, apply configured retention and deletion, and avoid copying raw records into logs just to make an audit convenient.

The release boundary in code

Before the incident, publication code treated storage inside an approved case as sufficient evidence:

// Tempting: the package inherits release authority from its case.
async function publish(packageId: string, session: PortalSession) {
const pkg = await packages.load(packageId);
await cases.requireOpenForRecipient(pkg.caseId, session.subject);
return delivery.publish(pkg.stagedObject, session.subject);
}

// Correct: reserve current authority, then publish one immutable version.
async function publishApproved(cmd: ReleaseCommand, caller: WorkloadIdentity) {
await workloads.requireReleaseWorker(caller, cmd.taskId, cmd.tenantId);

const proposalBytes = await proposals.loadCanonicalBytes(cmd.proposalId);
const proposal = ReleaseProposalV1.decodeStrict(proposalBytes);
const canonicalBytes = canonicalizeV1(proposal);
const proposalDigest = sha256(canonicalBytes);
const approval = await approvals.loadReceipt(proposalDigest);
const approvalState = await approvals.loadCurrent(approval.nonce);
const currentCase = await cases.load(proposal.caseId);
const currentPolicy = await policies.load(proposal.tenantId);
const object = await packages.headVersion(proposal.package.objectVersion);

if (!bytes.equal(proposalBytes, canonicalBytes) ||
approval.proposalDigest !== proposalDigest ||
approval.schemaVersion !== proposal.schemaVersion ||
approval.canonicalizationVersion !== proposal.canonicalizationVersion ||
approval.decision !== 'ALLOW' || approval.expiresAt <= clock.now() ||
approvalState.status !== 'APPROVED' ||
!currentPolicy.canApprove(approval.approver, approval.role) ||
currentCase.recipientSubject !== proposal.recipientSubject ||
object.version !== proposal.package.objectVersion ||
object.digest !== proposal.package.digest ||
object.length !== proposal.package.length ||
object.mediaType !== proposal.package.mediaType ||
object.formatVersion !== proposal.package.formatVersion) {
return cases.moveTo('REAUTHORIZE');
}
if (proposal.outputs.some((o) => o.eligibility.decision !== 'ALLOW')) {
return cases.moveTo('AMBIGUOUS_REVIEW');
}

// One transaction authenticates policy authority, compares every current
// revision, and moves this one-use nonce APPROVED -> RELEASING.
const reservation = await releaseLedger.reserve({
operationId: cmd.operationId, taskId: cmd.taskId, caller,
proposalDigest, approval, approvalState, currentCase, currentPolicy,
objectVersion: object.version, destination: proposal.destination,
});
if (!reservation.acquired) return cases.moveTo(reservation.nextState);

// operationId makes publication retry-idempotent. Readers remain denied
// until the ledger and ReleaseReceipt say this exact version is RELEASED.
try {
const publication = await delivery.publishImmutableVersion({
operationId: cmd.operationId, objectVersion: object.version,
proposalDigest, recipientSubject: proposal.recipientSubject,
destination: proposal.destination,
});
return releaseLedger.confirmReleased(
reservation, ReleaseReceipt.from(publication)
);
} catch {
await reconciliation.enqueue(cmd.operationId);
return cases.moveTo('RELEASE_PENDING');
}
}

async function serve(grantId: string, session: PortalSession, range?: ByteRange) {
const grant = await releaseLedger.loadReleased(grantId);
const currentCase = await cases.load(grant.caseId);
if (session.subject !== grant.recipientSubject ||
session.subject !== currentCase.recipientSubject) throw new Deny();
await portalPolicy.authorizeCurrentRead({grant, currentCase, session, range});
return portal.streamExactVersion(grant.objectVersion, range, AccessReceipt.record);
}

The reservation was the authorization point, not a check followed by an unrelated write. In one authoritative operation-ledger transaction it authenticated the Harborlight release workload, preserved the employer-policy context, compared current case, policy, approval, recipient, evidence, redaction, destination, channel, and object revisions, and conditionally spent the single-use nonce. The workload acted under the employer's configured policy; it was not Jana, it did not receive her token, and this design had no further multi-hop delegated actor.

Object storage and the ledger did not share a transaction. Publication therefore used the operation ID as an idempotency key and the exact immutable object version named by the proposal. A timeout, successful object write followed by receipt failure, or uncertain response moved the case durably to RELEASE_PENDING. Reconciliation checked object visibility, serving-key/grant state, and any ReleaseReceipt before completing or retrying; it never blindly republished. Readers remained denied until the control plane marked that exact version RELEASED.

Harborlight chose protected publication. At publication, the gateway placed one immutable encrypted version into a protected namespace and emitted a ReleaseReceipt bound to the configured recipient subject. That was not proof Jana had authenticated or read it. The portal rechecked the current release grant, case, recipient binding, and revocation state on every full or range read, served only that object version without archive regeneration, re-encoding, redirects, share links, or CDN substitution, and emitted an AccessReceipt for actual bytes served. Notification was only a locator.

Approval could be correct when Mara clicked and stale when publication began. The atomic reservation closed replay and the authorize-then-publish race for current control-plane state; idempotent publication and reconciliation closed uncertain completion. Revocation stopped future reads or destroyed the serving key for an unretrieved package, but Harborlight did not claim it could retract bytes already served. Partial and interrupted ranges remained in access receipts.

A fail-closed gateway introduced an availability dependency. Missing policy, lineage, labels, proposal bytes, receipt, current approval state, exact object version, or gateway availability moved the case durably to AMBIGUOUS_REVIEW, REAUTHORIZE, or RELEASE_PENDING. No operator or service could fall back to the old delivery path or publish directly from staging. A future break-glass route would have to be a separate, dual-controlled effect with its own exact proposal, short-lived authority, receipts, and post-event review.

Two designs Harborlight could defend

The incident did not produce a perfect identity graph or perfect data-flow labels. It left Harborlight with two credible architectures and different operating costs.

Design A: protected publication

In the protected-publication design, source adapters returned versioned records, typed relationships where available, and provenance metadata. Lumen proposed candidates, relationship types, evidence, conflicts, and redactions. Deterministic policy created case- and purpose-specific ReleaseEligibility decisions, rejected known contradictions, and sent UNKNOWN or CONFLICT to AMBIGUOUS_REVIEW; no confidence threshold converted a proposal into authority.

The versioned transformer created one write-once package and complete output lineage, including generated metadata. The manifest service created ReleaseProposalV1. Mara reviewed the unresolved evidence, exact output, and proposal. The approval store issued a separate expiring receipt for its digest.

At publication, an authenticated release workload supplied task and operation IDs. data-release-gateway recomputed equality, verified the exact object version, rechecked current case state, employer policy, scoped eligibility, source and redaction versions, recipient, destination, channel, expiry, and current approval state, then atomically reserved the nonce and revisions. It alone held publication authority. Idempotent publication and reconciliation handled the non-transactional object-store boundary; the portal separately authorized and receipted exact-version access.

This design concentrated enforcement immediately before final release. It was legible to reviewers and could be introduced without teaching every source system a complete shared label language.

It was not free. Harborlight's fictional operating model had previously completed 86% of requests without ambiguous-match review. The corrected path sent an additional 9% of all cases to review and raised median completion time from 11 to 19 hours. Source changes caused reapproval. Proposals, renderings, receipts, and previews added sensitive data to govern. The gateway and reviewer queue became availability and capacity dependencies.

Those numbers describe Harborlight, not an industry baseline.

Design B: closed-world continuous information-flow enforcement

In the continuous design, source adapters attached tenant, subject or relationship, classification, purpose, version, and provenance labels wherever authoritative evidence existed. Lumen could propose a new relationship, but the proposal stayed explicitly untrusted until policy or review resolved it.

Harborlight first declared a closed world of typed transformations and mandatory mediated sinks: redaction, OCR, summaries, archives, filenames, previews, logs, caches, notification bodies, protected publication, and portal service. These interfaces preserved input references, labels, transformation and policy versions, and output digests; uninstrumented connectors and outputs were denied. UNKNOWN was absorbing across joins and transformations until an explicit deterministic rule or scoped human decision resolved it. Lumen could not declassify its own output.

A deterministic controller evaluated every edge inside that declared boundary. Covert and unmodeled channels remained outside the claim, not silently assumed safe. The final exact-byte gateway from Design A still bound the immutable object version, recipient, case, purpose, destination, and expiry.

This design could reject a forbidden path earlier and maintain policy across more than one sink. Its cost moved outward. Every connector and transformation needed a shared relationship and provenance model. Labels were incomplete. Propagation through unstructured content was difficult. Conservative rules rejected some valid records. Connector integration, false rejection, and policy maintenance became permanent operating work.

Continuous enforcement did not promise perfect taint tracking or stand alone as a universal solution. It made unknowns explicit, denied new connectors without the contract, and added earlier defense around the same protected-publication boundary.

Both designs kept an ambiguity state. A system that silently omitted every uncertain record might prevent this disclosure while still failing the product obligation to deliver a complete, usable export or a clear ambiguity response.

The tests that separated a completed task from a safe one

Before the incident, Harborlight's strongest visible success signal was a completed package opened by the verified requester. The incident satisfied that signal.

The new evaluation harness graded eight questions independently for each trial:

  1. Candidate retrieval: Did Lumen propose the relevant records without drowning review in false candidates?
  2. Scoped release eligibility: Did current evidence and policy permit every included whole record, field/range, transformed output, and generated metadata item for this requester, case, and purpose?
  3. Redaction correctness: Did prohibited fields or third-party content remain in an otherwise eligible record?
  4. Approval equality: Did the executed proposal, immutable object version, recipient, purpose, source versions, redaction policy, channel, and expiry equal what Mara approved?
  5. Execution-time authorization: Were the workload, case, policy, recipient, eligibility, proposal, current approval state, and single-use reservation still valid at publication?
  6. Prohibited-byte outcome: Did any unauthorized byte become available through a package, preview, archive, cache, link, or alternate channel?
  7. Task outcome: Did the requester receive a complete, accessible export or an explicit ambiguity response?
  8. Evidence reconstruction: Could Harborlight join source version, Lumen rationale, eligibility, policy or review, approved proposal, publication receipt, served bytes, and authenticated recipient without retaining bearer tokens or complete exports in logs?

Task completion never compensated for a prohibited-byte failure.

The fault matrix included namesakes, former names, rehires, reassigned email, quoted names, multi-subject and partially releasable records, reviewer error, OCR collisions, conflicting attachment metadata, and generated filenames, cover pages, summaries, and archive metadata. It changed source objects, scoped eligibility, redaction versions, recipients, purposes, channels, policy versions, approval status, and expiry after review.

Canonicalization property tests permuted field order and exercised omitted/default values, Unicode, aliases, duplicate and unknown keys, schema and canonicalization versions, and every material field. Release tests used a receipt for the wrong proposal, replayed or concurrently reserved nonces, wrong-task/tenant/deployment/purpose workloads, cancellation after approval, policy change during reservation, publication-before-pickup revocation, recipient rebinding, duplicate workers, receipt-write failure, and retry after uncertain publication.

Serving tests substituted an object under the same case and package ID, rolled back versions, regenerated archives, introduced post-check transformations and stale CDN/cache content, and issued partial and range requests. Closed-world tests omitted labels, joined multiple inputs, and exercised OCR, summaries, previews, logs, archives, notification-derived output, and newly deployed connectors. Dependency timeouts, stale control-plane caches, partial outages, and operator bypass attempts had to land in the named fail-closed states without making the old delivery path reachable.

The harness tested ALLOW, DENY, and explicit review outcomes. It also failed designs that denied every ambiguous record and quietly called the export complete.

Harborlight measured review rate and queue age, decision dwell time, false inclusion and exclusion, safe completion latency, reapproval, gateway availability, temporary-artifact lifecycle, audit reconstruction, accessible completion, cost per safely accepted export, bulk/override rate, reversal, and reviewer disagreement. Admission control protected a staffed queue-age objective, with forecasted capacity and a second-review escalation pool. Saturation returned an explicit delay or ambiguity notice; it did not raise a confidence threshold, enable bulk approval, or reopen direct staging publication. Retrieval quality remained a model metric. It no longer stood in for release safety.

The question Elias used next

Elias tested whether the team had learned a privacy-export trick or a general authority model.

A manufacturing company's procurement agent could read purchase orders and supplier master data, write a settlement proposal to an internal case store, and ask a payment service to schedule an approved settlement. Each tool was narrow.

While resolving an invoice discrepancy, the model linked a valid factoring notice to the wrong supplier account. It proposed the correct amount to a different payee. A human had approved only “resolve the invoice discrepancy.”

Elias asked four questions:

  1. What composed path turned read and proposal permissions into a payment capability?
  2. Which authoritative evidence had to bind supplier, invoice, and payee?
  3. What exact proposal had to bind supplier, invoice, amount, currency, payee, evidence versions, purpose, and expiry?
  4. Which deterministic settlement boundary had to stop or execute the payment after checking the current world?

“Narrow the scopes,” “ask accounts payable,” and “improve the match” were useful supporting actions. None answered all four questions.

The surface had changed from employment data to supplier settlement. The rule had not:

A model may propose the path. Only a deterministic boundary with current, effect-specific authority may open the sink.

Where release authority lives

The incident sequence showed how six correct local decisions formed an unauthorized path. The final topology separates proposal, evidence, approval, and effect ownership, and shows the direct path from Lumen or staging to external delivery as blocked.

Loading interactive production topology…

The responsibility ledger for that topology is:

  1. Source adapters own versioned retrieval and source provenance; they do not authorize delivery.
  2. Employer domains and the controlled identity graph provide stable identifiers and typed relationship evidence where available; uncertainty stays explicit.
  3. Lumen proposes candidates, linkage evidence, conflicts, and redactions; it cannot approve its own relationship or release bytes.
  4. Deterministic policy and Mara's ambiguity review resolve supported, rejected, and unresolved relationships.
  5. The manifest service produces versioned canonical ReleaseProposalV1 bytes and governs the sensitive proposal and review-rendering lifecycle.
  6. The reviewer UI renders the exact proposal/output; the approval store issues a separate ApprovalReceipt and owns current revocation, supersession, reservation, and spend state. Approval evidence is not a delivery credential.
  7. An authenticated Harborlight release workload carries task, tenant, purpose, and operation identity under the employer-policy context; it does not impersonate Jana.
  8. data-release-gateway revalidates current authority, atomically reserves the one-use nonce and revisions, idempotently publishes the exact immutable object version, reconciles uncertain results, and emits a ReleaseReceipt. It alone owns publication authority.
  9. The authenticated portal reauthorizes every read and range for the current grant, case, recipient, and exact object version, then emits an AccessReceipt for actual bytes served.
  10. Audit and evaluation join source version, Lumen rationale, eligibility, policy, reviewer, proposal, approval, workload, publication, bytes served, and recipient while minimizing retained sensitive content.

Harborlight could now prove more than who called every tool. It could explain why the exact bytes released were authorized for the exact person who received them—or stop in ambiguity when it could not.

Concepts in this story5 concepts
Privilege compositionIdentity and security · Capability containment

The broader capability created when several individually permitted operations can be chained. Local least-privilege checks do not establish that the complete path or resulting effect is authorized.

Source-to-sink authorizationIdentity and security · Information-flow control

An authorization decision over the complete path from protected input through transformations to a consequential destination. It checks whether this data may reach this sink for the current purpose, not only whether each intermediate tool call is allowed.

Record-subject bindingData systems · Provenance and identity

The supported relationship between a versioned record and the person or entity it concerns. A model may propose this relationship from semantic evidence, but release authority requires corroborating source metadata, policy, or explicit review.

Effect-bound approvalAgentic systems · Approval binding

Human authorization tied to one canonical proposed effect: its target, parameters, evidence, constraints, destination, and expiry. A material change creates a new approval question instead of inheriting consent from the surrounding task.

Execution-time authorizationAgentic systems · Effect authorization

Re-evaluating current principal, workload, resource, purpose, policy, approval, and effect immediately before a consequential action. Permission observed during planning or approval is historical evidence, not automatic present authority.


Harborlight People, Lumen, Jana Li, Jian Li, Mara Chen, Elias Ward, case PR-2048, the incident, and all operating metrics are fictional. The workflow is a customer-configured product design, not a universal statement of legal requirements. Technical grounding includes Anthropic's 2026 containment report, OpenAI's 2026 source-to-sink agent design, the NIST Privacy Framework, NIST SP 800-53 Rev. 5, NIST Digital Identity Guidelines, W3C PROV, OWASP authorization guidance, OWASP IDOR prevention, OWASP logging guidance, RFC 8707 resource indicators, and RFC 9396 rich authorization requests. These sources support system principles; they do not describe Harborlight, its incident, its metrics, or one universal privacy process.