Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
2087999
feat: add portable transfer report domain contract
Ankit6149 Jul 30, 2026
32cc472
feat: add portable transfer ports
Ankit6149 Jul 30, 2026
a6792d3
feat: add versioned portable archive contract and integrity validation
Ankit6149 Jul 30, 2026
6ea9c9e
feat: add asset blob and transfer report adapters
Ankit6149 Jul 30, 2026
4294e05
feat: add portable metadata repository ports
Ankit6149 Jul 30, 2026
0b67580
feat: add portable metadata repositories
Ankit6149 Jul 30, 2026
b3ebe25
feat: implement preview import resume and rollback transfer service
Ankit6149 Jul 30, 2026
096761a
feat: add portable transfer state machine
Ankit6149 Jul 30, 2026
1f7c88f
feat: compose browser portable transfer application
Ankit6149 Jul 30, 2026
13336ac
test: cover portable archive security integrity and compatibility
Ankit6149 Jul 30, 2026
f9329d9
test: prove local cloud local transfer resume conflicts and rollback
Ankit6149 Jul 30, 2026
f3add8a
test: cover portable transfer UI state machine
Ankit6149 Jul 30, 2026
b47f516
test: cover portable metadata and browser blob adapters
Ankit6149 Jul 30, 2026
3a29271
chore: add guarded portable transfer domain migration
Ankit6149 Jul 30, 2026
634b08c
ci: add guarded portable transfer core gate
Ankit6149 Jul 30, 2026
e24b7ad
ci: expose portable transfer integration on draft PR
Ankit6149 Jul 30, 2026
ebd3ce4
fix: preserve current domain serialization behavior
Ankit6149 Jul 30, 2026
0fc47bb
test: include transfer reports in domain contract round trip
Ankit6149 Jul 30, 2026
43c2e08
test: inspect portable payload separately from exclusion audit
Ankit6149 Jul 30, 2026
efe84a3
ci: capture exact portable transfer failure summary
Ankit6149 Jul 30, 2026
8621c7c
test: capture portable transfer assertion
github-actions[bot] Jul 30, 2026
a94613c
test: capture portable transfer assertion
github-actions[bot] Jul 30, 2026
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
99 changes: 99 additions & 0 deletions .github/workflows/apply-portable-transfer-domain.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Apply portable transfer domain integration

on:
push:
branches:
- architecture/portable-transfer
pull_request:
branches:
- master
workflow_dispatch:

permissions:
contents: write

concurrency:
group: apply-portable-transfer-domain
cancel-in-progress: false

jobs:
integrate-and-verify:
if: github.actor != 'github-actions[bot]'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: architecture/portable-transfer
fetch-depth: 0

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Validate and apply domain migration
run: |
python -m py_compile scripts/apply-portable-transfer-domain.py
python scripts/apply-portable-transfer-domain.py
git diff --check

- name: Install frontend dependencies
working-directory: frontend
run: npm ci

- name: Run frontend tests
id: frontend_tests
working-directory: frontend
shell: bash
run: |
set +e
npm test > ../portable-transfer-test.log 2>&1
status=$?
echo "status=$status" >> "$GITHUB_OUTPUT"
exit 0

- name: Write compact failure summary
if: steps.frontend_tests.outputs.status != '0'
run: |
python - <<'PY'
from pathlib import Path
lines = Path('portable-transfer-test.log').read_text(errors='replace').splitlines()
indexes = [i for i, line in enumerate(lines) if line.startswith('not ok ')]
output = []
for index in indexes[:3]:
output.extend(lines[index:index + 28])
output.append('---')
Path('portable-transfer-failure-summary.txt').write_text('\n'.join(output) or 'Tests failed without a TAP not-ok block.')
PY
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -f portable-transfer-failure-summary.txt
git commit -m "test: capture portable transfer assertion"
git push origin HEAD:architecture/portable-transfer

- name: Fail when frontend tests fail
if: steps.frontend_tests.outputs.status != '0'
run: exit 1

- name: Audit and build frontend
working-directory: frontend
run: |
npm audit --omit=dev --audit-level=high
npm run build

- name: Remove temporary integration files
run: |
rm scripts/apply-portable-transfer-domain.py
rm .github/workflows/apply-portable-transfer-domain.yml
rm portable-transfer-test.log
rm -f portable-transfer-failure-summary.txt
rm -rf scripts/__pycache__

- name: Commit verified core
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "feat: add portable transfer archive and import engine"
git push origin HEAD:architecture/portable-transfer
31 changes: 31 additions & 0 deletions frontend/lib/application/browserTransferApplication.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createBrowserCampaignRepository } from "../infrastructure/adapters.mjs";
import {
createBrowserApprovalRepository,
createBrowserAssetRepository,
createBrowserBlobStorage,
createBrowserExportRepository,
createBrowserSourceArtifactRepository,
createBrowserTransferReportRepository,
} from "../infrastructure/transferAdapters.mjs";
import { createTransferApplication } from "../transfer/transferApplication.mjs";

export function createBrowserTransferApplication({
getStorage,
campaignKey = "signalflow_recovery_library",
clock,
idService,
signer = null,
} = {}) {
return createTransferApplication({
campaignRepository: createBrowserCampaignRepository({ getStorage, key: campaignKey }),
assetRepository: createBrowserAssetRepository({ getStorage }),
sourceArtifactRepository: createBrowserSourceArtifactRepository({ getStorage }),
approvalRepository: createBrowserApprovalRepository({ getStorage }),
exportRepository: createBrowserExportRepository({ getStorage }),
blobStorage: createBrowserBlobStorage({ getStorage }),
transferReportRepository: createBrowserTransferReportRepository({ getStorage }),
clock,
idService,
signer,
});
}
2 changes: 2 additions & 0 deletions frontend/lib/domain/contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const DOMAIN_KINDS = Object.freeze({
CONNECTION: "Connection",
USAGE_EVENT: "UsageEvent",
AUDIT_EVENT: "AuditEvent",
TRANSFER_REPORT: "TransferReport",
});

export const DOMAIN_CONTRACTS = Object.freeze({
Expand All @@ -36,6 +37,7 @@ export const DOMAIN_CONTRACTS = Object.freeze({
Connection: { idField: "connectionId", owner: "workspace", required: ["connectionId", "provider", "status"] },
UsageEvent: { idField: "usageEventId", owner: "workspace", required: ["usageEventId", "eventType"] },
AuditEvent: { idField: "auditEventId", owner: "workspace", required: ["auditEventId", "eventType"] },
TransferReport: { idField: "transferReportId", owner: "workspace", required: ["transferReportId", "archiveId", "status"] },
});

const FORBIDDEN_FIELD = /(api[_-]?key|access[_-]?token|refresh[_-]?token|oauth[_-]?token|client[_-]?secret|password|authorization|cookie|database|dbclient|request|response)/i;
Expand Down
5 changes: 5 additions & 0 deletions frontend/lib/domain/ports.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
export const PORT_CONTRACTS = Object.freeze({
campaignRepository: ["list", "get", "upsert", "remove"],
assetRepository: ["list", "get", "upsert", "remove"],
sourceArtifactRepository: ["list", "get", "upsert", "remove"],
approvalRepository: ["list", "get", "upsert", "remove"],
exportRepository: ["list", "get", "upsert", "remove"],
blobStorage: ["put", "get", "remove"],
transferReportRepository: ["list", "get", "upsert", "remove"],
archiveSigner: ["sign", "verify", "describe"],
jobQueue: ["enqueue", "get", "cancel"],
providerAdapter: ["test", "generate"],
connectorAdapter: ["status", "publish"],
Expand Down
Loading