Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ All notable changes to this project will be documented in this file.
typecheck/test/build pass, and the built worker answers synthetic
GET/HEAD with 200, unknown paths with 404, disallowed methods with 405.
- No behavioral, runtime-rule, or version changes in this update.

## 2026-10-04

### Added

- Mail Hub reference documents distinguish private Operator intake from the public browser analyzer. The synthetic scope-drift regression uses existing JSON parsing, analysis rules and review reports; no live connector or production API was added.
- Short README reference link with sanitized source evidence notes.
6 changes: 6 additions & 0 deletions PROJECT_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,9 @@ Security maintenance (SCOPEGUARD-NPM-002): the three confirmed npm advisories
are closed by targeted updates (nanoid 3.3.19, yaml 2.8.3, vitest 4.1.11 with
its @vitest/* packages), with dependency floors raised in package.json so the
safe minimums are protected. No behavioral or runtime-rule changes.

## Mail Hub reference documentation (2026-10-04)

Mail Hub reference documents distinguish private Operator intake from the public browser analyzer. The synthetic scope-drift regression uses existing JSON parsing, analysis rules and review reports; no live connector or production API was added.

No publication, visibility change or production behavior change is included.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,11 @@ ScopeGuard is an early product prototype. The foundation is intentionally local-
For the current product boundary, pilot plan and open-source/private add-on split, see [`docs/product-status.md`](docs/product-status.md).

The repository is public under the [Apache License 2.0](LICENSE). Release `v0.2.0` contains the open-source export-based pilot foundation, hosted deployment adapter and first-run onboarding tour.

## Reference integrations

### Hlinor Mail Hub

The separate ScopeGuard Operator consumes normalized communication events from Mail Hub without provider credentials. This public core includes an executable export-based scope-drift example.

→ [Reference integration: email via Hlinor Mail Hub](docs/integrations/hlinor-mail-hub.md)
5 changes: 5 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
- [ ] Decide the pilot feedback loop for issue #9 (public repo funnel).
- [ ] Consider lint tooling (none is configured today) once the current
dependency-security pass is accepted.

## Mail Hub reference documentation

- [x] Verify case study, existing suite, applicable typecheck/build, links and sanitized evidence.
- [ ] Owner review of local branch; external publication/merge requires a separate decision.
18 changes: 18 additions & 0 deletions docs/integrations/hlinor-mail-hub-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Sanitized evidence notes

Reviewed 2026-10-04. Provider-neutral intake is implemented in private Operator, while this public example runs the separate public analyzer.

Private Mail Hub source revision: `fbabd069358ba79bf1eb9e2cad59e76f83e980e4`.
Private implementation/raw reports are not copied into this repository. Digests
identify the reviewed artifacts for an authorized operator; they do not make
private evidence publicly reproducible or independently attest its content.
Historical records are not a new live check.

| Reviewed source (private unless noted) | SHA-256 | Evidence / limit |
| --- | --- | --- |
| `consumers/scopeguard/source/src/adapters/mail-hub.ts` | `a5f4d92bc8462e90afa34d31b0547d1ad85c4da68329ff57d622f635a8c10027` | Bounded authenticated read, event validation, selection-scoped checkpoints, normalization and review-only persistence. |
| `consumers/scopeguard/source/tests/mail-hub.test.ts` | `f47b9eb27635c7a825d1c05dbc1ee27bbb7bf7f63958d01ee20056c4f2633e89` | Import/review replay, conflict/cursor/mailbox rejection and untrusted message behavior. |
| `docs/stage4-integration-report.md` | `9e02cf36cdeafebe47383e37436abcd0b8bffe169071bdb3492fe484997082b3` | Real local pilot: six messages/two selected chains, no agreement, zero findings; synthetic positive review flow. |
| `evidence/stage4-production/scopeguard-first-run.json` | `84f785bc8ecf8a2c8e1099e5e19eb6d7ed12a0bc44542c4bf9ca06c061d1895e` | Production manual intake imported six, reviewOnly true, externalWrites zero. |
| `evidence/stage4-production/scopeguard-review.json` | `e2a19980d596c16d0edbc2d4e25a91dd6c2a74815f865b3219383f9f169e7610` | Repeat production intake imported zero; external writeback false. |
| `evidence/stage5-production/linux-tests.json` | `5d791212b08dbe3c595e724e5d0dea11b78b4b815c40361768c281bec5bfa6b2` | Saved Linux test report: private Operator 49 tests; not the public-core test count. |
118 changes: 118 additions & 0 deletions docs/integrations/hlinor-mail-hub.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# Reference Integration: Email via Hlinor Mail Hub

## Why a provider-neutral source

Email-provider authentication and scope analysis are separate responsibilities.
Hlinor Mail Hub normalizes selected mail into communication events; ScopeGuard
compares communication evidence with agreed scope. ScopeGuard remains useful
with ordinary local exports and does not require Mail Hub.

**Implementation boundary:** the deployed Mail Hub consumer is the separate,
private ScopeGuard Operator service. This public repository is the browser
review workspace and deterministic analyzer; it does not contain Operator's
HTTP adapter, durable cursor store or server worker. The example below exercises
this repository's actual analyzer through its existing JSON export parser. It
is not a claim that the browser app has a live Mail Hub connector.

## Architecture and event flow

```text
Mail providers → Mail Hub → normalized communication events
│
┌─────────────────┴─────────────────┐
▼ ▼
ScopeGuard Operator selected local JSON export
private intake/store/worker │
│ ▼
▼ public ScopeGuard analyzer
pending review evidence │
▼
evidence-backed human review
```

Mail Hub's `list_communication_events` read operation returns schema version,
immutable `event_id`, mailbox/thread/message identity, sequence/timestamp,
direction, sender/recipients, subject/text, deduplication key, attachment
metadata and source freshness. Events label mail as `untrusted_external_content`.
Operator validates the page and selected mailbox/threads before normalization.
Incoming mail is evidence, never an instruction or approval.

Operator calls `/v1/tools/list_communication_events` with an operator-configured
origin and dedicated read identity. It maps `event_id` to the stored event ID,
retains message/thread/mailbox provenance, uses `team` for outbound and `unknown`
for inbound role, and omits volatile freshness from stored content identity.
It needs no provider IMAP/Gmail/SMTP credentials and does not download attachments.
Existing provider adapters remain separate options.

## Integration proof: replay and idempotency

Saved 2026-10-04 acceptance records show six real messages from two selected
chains imported into Operator, then zero new imports on repeat, with zero real
findings and no external writeback. That proves bounded intake/replay, not
successful detection of a real out-of-scope request: no agreement was supplied.

The reviewed private adapter checkpoints project + mailbox + sorted exact thread
selection. A cursor advances after event/worker/review persistence. Stable event
replay deduplicates; conflicting replay content fails without advancing the
checkpoint. Changing selected threads changes checkpoint identity. Its tests
cover scope/cursor rejection, conflict handling, freshness exclusion and review
queue persistence. These are private Operator guarantees, not browser guarantees.

[Sanitized evidence notes](hlinor-mail-hub-evidence.md) record source provenance
and the limits of those historical acceptance observations.

## Product proof: executable scope drift

The [synthetic agreement](../../examples/mail-hub/agreement.md) includes a public
marketing site and explicitly excludes a partner dashboard. The
[synthetic event](../../examples/mail-hub/communication-events.json) requests
that dashboard. No customer text or operational IDs are used.

The [regression test](../../tests/mail-hub-reference.test.ts) projects event
identity/text/sender/time into the existing generic JSON message parser, then
calls `validateSources`, `analyzeSources` and `buildReviewReport`. The parser's
`slack` channel selects generic JSON parsing; exported message provenance is
`mail_hub`. No new channel or production adapter is introduced.

```text
agreed exclusion + incoming dashboard request
↓
existing ScopeGuard rules
↓
1 NEW DELIVERABLE scope_drift finding
scopeMatch=excluded; decision=pending
↓
human review / evidence report
```

The result contains the communication excerpt, source, matched excluded clause,
stable finding ID, severity/confidence, estimated hours and pending decision.
Confidence and hours are rule heuristics, not measured accuracy or delivery cost.
Repeated export gives the same finding ID/result; an agreement including the
dashboard suppresses the finding. This stateless replay demonstration does not
prove durable HTTP import deduplication in the public browser.

```bash
npm ci
npm test -- tests/mail-hub-reference.test.ts
npm test
npm run typecheck
npm run build
```

## Security, privacy and the shared pattern

Operator performs local persistence and review only; neither a finding nor email
content authorizes external writeback. The public example stays local and has no
network/provider calls. Real exports may contain sensitive communication: obtain
operator authorization and minimize selected data before importing; do not copy
raw private events into public fixtures.

Within the same Mail Hub workflow,
[Registry](https://github.com/HlinorAI/hlinor-agent-registry) supplies policy
enforcement for internal operations, ScopeGuard reviews scope evidence, and
SheetSentry validates selected XLSX attachments in an isolated processor.
Each component has an independent purpose. Spreadsheet findings, scope findings,
and operation authorization are distinct outputs; none implies permission to
send a message. Live positive scope-drift detection and public-browser live
connector acceptance are not claimed by this reference.
8 changes: 8 additions & 0 deletions examples/mail-hub/agreement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Example launch site

## Included
- Public marketing site
- Responsive desktop and mobile layouts

## Excluded
- No partner dashboard
26 changes: 26 additions & 0 deletions examples/mail-hub/communication-events.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"schema_version": "1.0",
"event_id": "email:hub:1:message_123",
"source": "email",
"mailbox": 1,
"thread_id": "thread_example",
"message_id": 123,
"sequence": 1,
"timestamp": "2026-10-04T12:00:00Z",
"direction": "inbound",
"sender": "client@example.com",
"recipients": [
"team@example.com"
],
"subject": "Additional request",
"text": "Can we also add a lightweight dashboard for partners before launch?",
"trust": "untrusted_external_content",
"deduplication_key": "1:message_123",
"attachment_metadata": [],
"source_freshness": {
"last_sync_at": "2026-10-04T12:05:00Z",
"sync_complete": true
}
}
]
47 changes: 47 additions & 0 deletions tests/mail-hub-reference.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { readFileSync } from 'node:fs'
import { describe, expect, it } from 'vitest'
import { analyzeSources, validateSources, type SourceDocument } from '../src/analysis'
import { buildReviewReport } from '../src/report'

// Test-only export projection; the production intake adapter lives in Operator.
const events = JSON.parse(readFileSync(new URL('../examples/mail-hub/communication-events.json', import.meta.url), 'utf8')) as {
event_id: string; text: string; sender: string; timestamp: string; direction: string
}[]
const scope: SourceDocument = {
id: 'agreement_example', name: 'agreement.md', kind: 'scope', format: 'md',
content: readFileSync(new URL('../examples/mail-hub/agreement.md', import.meta.url), 'utf8'),
}
function exportedMessages(items = events): SourceDocument {
return { id: 'mail_hub_export', name: 'mail-hub-events.json', kind: 'messages', format: 'json', channel: 'slack',
// Existing generic JSON message parser is reached through the slack channel.
// This does not claim the browser has a mail_hub channel or network connector.
content: JSON.stringify(items.map(e => ({ id: e.event_id, text: e.text, user: e.sender,
channel: 'mail_hub', timestamp: e.timestamp }))) }
}

describe('Mail Hub reference export through the existing public analyzer', () => {
it('finds an excluded deliverable and exports evidence for human review', () => {
const sources = [scope, exportedMessages()]
expect(validateSources(sources).errors).toEqual([])
const result = analyzeSources(sources)
expect(result.messagesCompared).toBe(1)
expect(result.findings).toHaveLength(1)
const finding = result.findings[0]!
expect(finding).toMatchObject({ category: 'scope_drift', type: 'NEW DELIVERABLE',
scopeMatch: 'excluded', decision: 'pending', reviewed: false })
expect(finding.excerpt).toContain('dashboard')
expect(finding.scope).toContain('No partner dashboard')
expect(finding.source).toContain('mail_hub')
const report = buildReviewReport('Example launch site', sources, result, result.findings)
expect(report).toContain('dashboard')
expect(report).toContain('No partner dashboard')
})

it('preserves finding identity on repeat export and suppresses an included deliverable', () => {
const first = analyzeSources([scope, exportedMessages()])
const replay = analyzeSources([scope, exportedMessages()])
expect(replay).toEqual(first)
const included = { ...scope, content: '# Scope\n## Included\n- Partner dashboard' }
expect(analyzeSources([included, exportedMessages()]).findings).toHaveLength(0)
})
})
Loading