Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/tally/compatibility/compatibility-matrix.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"schema_version": 1,
"bridge_commit_sha": "be1c20cc3fd66fa1ece196505c69f26e555e4b8e",
"compatibility_surface_sha256": "5c8258c4cc7635b3e73fb86eccc8b839c78f236753653e43c2ddc0b78e549ad0",
"compatibility_surface_sha256": "b00abcb20bee72d2b127ea0a37a3d2210f8382ab0c589ec2abe5a4d8b595379c",
"claims": [
{
"claim_id": "erp9-6-6-3-windows-education-xml-one-company",
Expand Down
10 changes: 5 additions & 5 deletions docs/tally/compatibility/compatibility-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
},
{
"path": "scripts/tally-company-selection.test.mjs",
"sha256": "06dd510f72ab2365c64a5a14b79b482633abee6b09c1ed0066f2d4ce98012c42"
"sha256": "16bf5a1458afffb614b2e71e5147eb393088ca30f7c4f6eae19f82346ffc8f00"
},
{
"path": "src-tauri/Cargo.lock",
Expand Down Expand Up @@ -419,16 +419,16 @@
},
{
"path": "src/main.tsx",
"sha256": "93968d5670001e371da85246e1c25ccc7afbac3a75b5118a5e9972f577dbf70d"
"sha256": "919190e826f06940fbe2640c8e453f2e04de1262a6ad3e41f28430b8467491c2"
},
{
"path": "src/styles.css",
"sha256": "2a1123727cd527e54190236719787dbdc1420c5099712011290eab9e40590ccd"
"sha256": "3d931bf809e7453031e2b5433f2fcee331879353662018876bb89de382a6a6f9"
},
{
"path": "src/tally-company-selection.ts",
"sha256": "5a5c6eaaba234c3cbda52dfa040ed3314f79e535f744b87bc14d8d76a2299811"
"sha256": "dd033010711ca01a7a395a531285c6ef187a359640a156cbc8da7281e11a39ea"
}
],
"manifest_sha256": "5c8258c4cc7635b3e73fb86eccc8b839c78f236753653e43c2ddc0b78e549ad0"
"manifest_sha256": "b00abcb20bee72d2b127ea0a37a3d2210f8382ab0c589ec2abe5a4d8b595379c"
}
42 changes: 42 additions & 0 deletions scripts/tally-company-selection.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import test from "node:test";

import {
applyProbeCompanySelectionTransition,
companyDiscoveryPrompt,
clearCompanyScopedState,
reconcileProbeCompanySelection,
} from "../src/tally-company-selection.ts";
Expand Down Expand Up @@ -117,6 +118,20 @@ test("an automatic probe drop clears old company state before installing a usabl
);
});

test("discovery requested after a dropped selection remains current after cleanup", () => {
const state = companyScopedState();
const probeResultsVersion = state.tallyResultsVersion;

applyProbeCompanySelectionTransition("old-company", [], {
clearDroppedCompanyScope: () => clearCompanyScopedState(cleanupFor(state)),
installProbeState: () => {},
});
const discoveryResultsVersion = state.tallyResultsVersion;

assert.notEqual(probeResultsVersion, state.tallyResultsVersion);
assert.equal(discoveryResultsVersion, state.tallyResultsVersion);
});

test("a manual company selection clears the existing review and all company-scoped state", () => {
const state = companyScopedState();

Expand Down Expand Up @@ -163,3 +178,30 @@ test("a selected company retained by the probe is not reported as dropped", () =
{ selectedCompany: "", dropped: false },
);
});

test("a successful probe prompts the operator to explicitly choose a discovered company", () => {
assert.deepEqual(companyDiscoveryPrompt("", ["company-a"]), {
companyCount: 1,
heading: "1 company discovered",
detail: "Bridge identified the current Tally company list. Choose one explicitly before reading or saving any company-scoped data.",
actionLabel: "Choose company",
});
assert.deepEqual(companyDiscoveryPrompt("", ["company-a", "company-b"]), {
companyCount: 2,
heading: "2 companies discovered",
detail: "Bridge identified the current Tally company list. Choose one explicitly before reading or saving any company-scoped data.",
actionLabel: "Choose company",
});
assert.equal(companyDiscoveryPrompt("company-a", ["company-a"]), null);
assert.equal(companyDiscoveryPrompt("", []), null);
});

test("a direct compatibility listing prompts verification instead of selecting an untrusted identity", () => {
assert.deepEqual(companyDiscoveryPrompt("", [], 1), {
companyCount: 1,
heading: "1 company listed for verification",
detail: "Tally returned a compatibility company listing. Verify the intended company before Bridge treats its identity as evidence or enables company-scoped reads.",
actionLabel: "Verify company",
});
assert.equal(companyDiscoveryPrompt("selected-company", [], 1), null);
});
40 changes: 39 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import React from "react";
import ReactDOM from "react-dom/client";
import { Activity, Building2, Cable, CircleHelp, Cloud, Database, FileText, FolderOpen, KeyRound, Play, RefreshCw, ShieldCheck, UploadCloud } from "lucide-react";
import { invoke } from "@tauri-apps/api/core";
import { applyProbeCompanySelectionTransition, clearCompanyScopedState } from "./tally-company-selection";
import {
applyProbeCompanySelectionTransition,
clearCompanyScopedState,
companyDiscoveryPrompt,
} from "./tally-company-selection";
import "./styles.css";

type TallyConfig = {
Expand Down Expand Up @@ -977,6 +981,23 @@ function App() {
);
setSelectedCompany(selection.selectedCompany);
void refreshPersistedCompanyProfiles();
setUntrustedDiscoveredCompanies([]);
setUntrustedDiscoveryError(null);
setUntrustedDiscoveryCompleted(false);
if (result.profile.transports.xml_http?.safe_reason_code === "direct_company_report_untrusted") {
Comment thread
lamemustafa marked this conversation as resolved.
const discoveryResultsVersion = tallyResultsVersion.current;
try {
const discovered = await invoke<UntrustedCompanyCandidate[]>("fetch_tally_companies", { config });
if (discoveryResultsVersion === tallyResultsVersion.current) {
setUntrustedDiscoveredCompanies(discovered);
setUntrustedDiscoveryCompleted(true);
}
} catch (error) {
if (discoveryResultsVersion === tallyResultsVersion.current) {
setUntrustedDiscoveryError(toOperatorError(error));
}
}
}
}
} catch (error) {
if (resultsVersion === tallyResultsVersion.current) {
Expand Down Expand Up @@ -1846,6 +1867,11 @@ function App() {
const gstDraftComplete = draft !== null && draft.missing_fields.length === 0;
const selectedCompanyRecord = companies.find((company) => tallyCompanyKey(company) === selectedCompany);
const selectedCompanyLive = !!selectedCompanyRecord && liveCompanyKeys.includes(tallyCompanyKey(selectedCompanyRecord));
const discoveredCompanyPrompt = companyDiscoveryPrompt(
selectedCompany,
liveCompanyKeys,
untrustedDiscoveredCompanies.length,
);
React.useEffect(() => {
const mirrorCompanyId = selectedCompanyRecord?.mirror_company_id;
setFixtureStatus(null);
Expand Down Expand Up @@ -1995,6 +2021,18 @@ function App() {
<button className="secondary-action" type="button" onClick={() => setView("companies")}>Change setup</button>
</section>

{discoveredCompanyPrompt && (
<section className="company-discovery-notice" role="status" aria-live="polite">
<div>
<strong>{discoveredCompanyPrompt.heading}</strong>
<span>{discoveredCompanyPrompt.detail}</span>
</div>
<button className="primary" type="button" onClick={() => setView("companies")}>
{discoveredCompanyPrompt.actionLabel}
</button>
</section>
)}

{["dashboard", "companies", "mirror"].includes(view) && (
<section className="operator-question-grid" aria-label="Tally operator summary">
<article><span>Verified baseline</span><strong>{verifiedBaseline}</strong></article>
Expand Down
34 changes: 34 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,34 @@ h2 {
overflow-wrap: anywhere;
}

.company-discovery-notice {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
border: 1px solid #9bc9b6;
border-left: 4px solid #0f766e;
border-radius: 8px;
background: #f0fdf7;
color: #183b2e;
margin: 0 0 16px;
padding: 14px 16px;
}

.company-discovery-notice div {
display: grid;
gap: 4px;
}

.company-discovery-notice span {
color: #416257;
line-height: 1.45;
}

.company-discovery-notice .primary {
flex: 0 0 auto;
}

.operator-question-grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
Expand Down Expand Up @@ -1079,10 +1107,16 @@ summary:focus-visible,
}

.company-context-bar,
.company-discovery-notice,
.company-profile-grid,
.gap-item {
grid-template-columns: 1fr;
}

.company-discovery-notice {
align-items: stretch;
flex-direction: column;
}
}

@media (max-width: 520px) {
Expand Down
37 changes: 37 additions & 0 deletions src/tally-company-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export type ProbeSelectionTransition = {
dropped: boolean;
};

export type CompanyDiscoveryPrompt = {
companyCount: number;
heading: string;
detail: string;
actionLabel: string;
};

export type ProbeSelectionEffects = {
clearDroppedCompanyScope: () => void;
installProbeState: () => void;
Expand Down Expand Up @@ -43,6 +50,36 @@ export function applyProbeCompanySelectionTransition(
return transition;
}

export function companyDiscoveryPrompt(
selectedCompany: string,
liveCompanyKeys: readonly string[],
untrustedCompanyCount = 0,
): CompanyDiscoveryPrompt | null {
if (selectedCompany !== "") return null;

if (liveCompanyKeys.length > 0) {
const companyCount = liveCompanyKeys.length;
const companyLabel = companyCount === 1 ? "company" : "companies";
return {
companyCount,
heading: `${companyCount} ${companyLabel} discovered`,
detail: "Bridge identified the current Tally company list. Choose one explicitly before reading or saving any company-scoped data.",
actionLabel: "Choose company",
};
}

if (untrustedCompanyCount === 0) return null;

const companyCount = untrustedCompanyCount;
const companyLabel = companyCount === 1 ? "company" : "companies";
return {
companyCount,
heading: `${companyCount} ${companyLabel} listed for verification`,
detail: "Tally returned a compatibility company listing. Verify the intended company before Bridge treats its identity as evidence or enables company-scoped reads.",
actionLabel: "Verify company",
};
}

export function clearCompanyScopedState(cleanup: CompanyScopeCleanup) {
cleanup.clearQualifiedReadReview();
cleanup.clearPassportSnapshot();
Expand Down