API — ICoreBlocks/ICoreAccount
The public contract of 15 header(s) under src/ICoreBlocks/ICoreAccount — 30 class/struct definition(s), 277 declaration(s). Each section shows the header's banner and its public (and protected-virtual) surface exactly as the file writes it.
ICoreActiveTime.h#
src/ICoreBlocks/ICoreAccount/ICoreActiveTime.h
ICoreActiveTime#
ICoreActiveTime.h:33 · class · pImpl · 10 declaration(s)
ICoreActiveTime — how long the user was actually USING the application.
class ICoreActiveTime {
public:
using TimePoint = std::chrono::system_clock::time_point;
static constexpr int IDLE_AFTER_SECONDS = 5 * 60;
ICoreActiveTime();
~ICoreActiveTime();
ICoreActiveTime(const ICoreActiveTime&) = delete;
ICoreActiveTime& operator=(const ICoreActiveTime&) = delete;
// The user did something: a key, a click, a command. Cheap enough to call
// from an event filter — it is two comparisons and an assignment.
void noteInteraction(TimePoint now);
// The window gained or lost focus. Losing it settles the interval
// immediately rather than waiting out the idle timeout.
void setFocused(bool focused, TimePoint now);
// Accumulated active seconds, settled up to `now`. Const: asking must never
// move the counter, or a diagnostics panel that polls would inflate it.
[[nodiscard]] int activeSeconds(TimePoint now) const;
[[nodiscard]] bool isActive(TimePoint now) const;
// For a heartbeat that reports and continues. Settles the interval so the
// next one starts from `now`, and returns the same value activeSeconds()
// would have.
int settle(TimePoint now);
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreFeatureGate.h#
src/ICoreBlocks/ICoreAccount/ICoreFeatureGate.h
ICoreFeatureGate#
ICoreFeatureGate.h:73 · class · nested Decision · 7 declaration(s)
ICoreFeatureGate — the one place a licence turns into a refusal.
class ICoreFeatureGate {
public:
ICoreFeatureGate() = delete;
// An answer plus what to say about it. The text is HERE rather than at the
// call sites so that two walls for one cause cannot describe it two ways —
// "not licensed" and "your licence expired" are different support tickets.
struct Decision {
bool allowed = true;
std::string title; // for a notification's heading; empty when allowed
std::string reason; // one sentence for the user; empty when allowed
// Body in the .cpp: the header surface rule permits none here.
[[nodiscard]] bool ok() const;
};
// Whether one code-generation target may be produced. `target` is the
// export target's code type as the user configured it ("Rust", "VHDL",
// "PLC ST", ...); matching is case-insensitive, and an EMPTY target is
// allowed — an unconfigured target fails later, for its own reason, and a
// licence message would be a wrong diagnosis.
[[nodiscard]] static Decision codegenTarget(std::string_view target);
// Whether the solver may run. Refused at EXPIRED and at UNLICENSED — the
// second since A3.5, and ICoreLicenseBanner had already been promising it.
[[nodiscard]] static Decision simulation();
// Whether one capability is available. The generic form, for the walls
// that map onto a single Feature.
[[nodiscard]] static Decision feature(ICoreFeature feature);
// Whether a licence gate is BOUND to this process — see the rule at the
// top. False means nothing resolved a licence at all and the gates allow
// everything; it does NOT mean "licensed", and an Unlicensed bound session
// answers true here and is refused.
[[nodiscard]] static bool enforcing();
// The capability's name in a sentence: "loading a block somebody else
// wrote", not "PluginLoad". Public because the walls that refuse for two
// reasons at once want to name the feature without re-spelling it.
[[nodiscard]] static std::string_view featureDescription(ICoreFeature feature);
// Every branch above, against hand-built licences, with no gate bound and
// with one bound in each state. Run by the `account` suite.
static int selfTest(std::vector<std::string>* failures);
};
};
ICoreJwtVerifier.h#
src/ICoreBlocks/ICoreAccount/ICoreJwtVerifier.h
ICoreJwtVerifier#
ICoreJwtVerifier.h:68 · class · bases public ICoreTokenVerifier · pImpl · nested TrustedKey · 9 declaration(s)
ICoreJwtVerifier — the real ICoreTokenVerifier.
class ICoreJwtVerifier : public ICoreTokenVerifier {
public:
// One trusted key. `publicKey` is what `signing_keys.public_key` holds:
// an unpadded base64url raw Ed25519 public key. Lowercase hex is accepted
// too — ICoreSignatureVerifier disambiguates by length.
struct TrustedKey {
std::string kid;
std::string publicKey;
};
// The keys compiled into this build. Rotation is additive at the server
// (insert trusted-but-not-active, wait a token lifetime, then flip), so
// this is a SET and not a single key — a build that held one key would
// stop working the moment a key rotated.
explicit ICoreJwtVerifier(std::vector<TrustedKey> trustedKeys);
// The pinned set. It is EMPTY in this build and every token is therefore
// refused with "untrusted_kid" — see pinnedKeys() below. That is the
// fail-closed direction: the alternative to an empty table is a table with
// a placeholder key in it, and a placeholder key is a key somebody has the
// private half of.
ICoreJwtVerifier();
~ICoreJwtVerifier() override;
[[nodiscard]] ICoreVerifiedToken verify(const std::string& token) const override;
// What this build will accept. EMPTY unless the build was configured with
// -DICORE_JWT_PUBLIC_KEYS="<kid>:<key>[;<kid>:<key>]" — the same shape
// ICORE_RELEASE_PUBLIC_KEYS uses for the updater's release key, plus the
// kid, because a JWT names its key and a release signature does not.
//
// ⚠ THE KEY IS STILL MISSING (A0.6). Nothing defines that macro today, so
// every token is refused with "untrusted_kid". Making it configurable is
// the plumbing half: publishing the key is a configure argument rather
// than a C++ edit, and it stays a thing somebody has to TYPE.
//
// ⚠ DO NOT give the macro a default value in CMakeLists.txt. A default is
// a placeholder wearing a build system, and a placeholder is a key
// somebody has the private half of. ⚠ DO NOT make it a runtime fetch — a
// key downloaded over the network is a key an attacker who owns the
// network chooses.
[[nodiscard]] static std::vector<TrustedKey> pinnedKeys();
// "<kid>:<key>[;<kid>:<key>]" -> the table. Public because it is the part
// worth testing: a malformed entry is DROPPED rather than repaired or made
// fatal, and during a rotation — the one time two keys are present — a
// typo in one must not take out the other.
[[nodiscard]] static std::vector<TrustedKey> parseKeyTable(std::string_view text);
// "icoreblocks-desktop" and "https://systemsicore.com". Named here rather
// than spelled at the comparison, so the suite asserts the same strings the
// check uses.
[[nodiscard]] static std::string_view expectedAudience();
[[nodiscard]] static std::string_view expectedIssuer();
// Returns the failure count and appends a sentence per failure (A7.1).
// Runs entirely on keys it makes up, so it proves the verifier without
// depending on a production key existing.
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreLicenseBinding.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseBinding.h
The one seam between the public icore::License view and the gate that owns the token. Internal: it is deliberately NOT in include/icore/, because a consumer able to install a gate is a consumer able to install a gate that says yes to everything.
The shell calls this once, after it has built a gate, and before the editor window is created. Passing nullptr unbinds — which is what a sign-out that tears the gate down does, and it leaves icore::License answering Unlicensed rather than dangling.
⚠ THE VIEW HOLDS A RAW POINTER AND DOES NOT OWN IT. The gate outlives the binding by construction (the shell owns both, and unbinds first); if that ever stops being true this is the line that has to change, not the callers.
Declares no class of its own — see the file.
ICoreLicenseClient.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseClient.h
ICoreLicenseClient — the only thing in this tree that talks to the licence server, and the seam every test replaces.
⚠ THE ONE NETWORK PEER IS https://systemsicore.com/api. No Supabase URL, no anon key, no PostgREST, no Supabase client of any kind, in any form. The Worker holds service_role and mints tokens; the app holds nothing. There is deliberately NO way to point this class somewhere else — not a constructor argument, not a setter, not an environment variable. A licence client whose peer is configurable is a licence client that can be pointed at a machine the user controls.
⚠ REFUSED AND UNREACHABLE ARE DIFFERENT ANSWERS, and getting this wrong is the most expensive mistake in the account manager. ICoreLicenseStateMachine turns Refused into EXPIRED immediately and Unreachable into 30 days of full
ICoreLicenseChoice#
ICoreLicenseClient.h:43 · struct · 0 declaration(s)
What one call came back with.
struct ICoreLicenseChoice {
public:
std::string id; // the licence's uuid -- what gets sent back
std::string maskedKey;
std::string tierName; // "Individual", "Team", ...
std::string planName;
std::string expiresAt; // ISO 8601, or empty for perpetual
int seatsUsed = 0;
int seatCount = 0;
};
};
ICoreLicenseReply#
ICoreLicenseClient.h:53 · struct · 0 declaration(s)
struct ICoreLicenseReply {
public:
enum class Outcome {
Ok,
Refused,
Unreachable
};
Outcome outcome = Outcome::Unreachable;
int httpStatus = 0; // 0 when no response line arrived
std::string token; // the signed JWT, on Ok
std::string errorCode; // "seat_limit_reached", "revoked", ...
std::string message; // human-readable, may be empty
// ---- the account route's second step ---------------------------------
//
// Filled ONLY when errorCode == "choose_license", which is not a failure:
// the account holds more than one licence and the person picks. `choices`
// is what to show them and `choiceTicket` is what proves, for the next five
// minutes, that the password was already checked -- so the second request
// carries a ticket and a licence id instead of the credentials again.
//
// ⚠ THE TICKET IS NOT A LICENCE AND IS NEVER STORED. It is signed by the
// same key but carries a different audience, which ICoreJwtVerifier
// refuses, so it cannot be adopted as one even by mistake.
std::vector<ICoreLicenseChoice> choices;
std::string choiceTicket;
};
};
ICoreActivationRequest#
ICoreLicenseClient.h:82 · struct · 0 declaration(s)
What an activation needs to say about itself.
struct ICoreActivationRequest {
public:
// ⚠ THREE SHAPES, AND EXACTLY ONE OF THEM IS FILLED IN. The Worker's
// activate route dispatches on which:
//
// licenseKey a licence key (the original route)
// email + password an account (2026-08-24)
// selectionTicket + licenseId that account's choice, when it holds
// more than one licence
//
// ⚠ THE ACCOUNT ROUTE ONLY WORKS AGAINST A WORKER THAT HAS THE ACCOUNT
// DEPLOY. An older one answers `key and fp are required`, and the client
// maps that back to `email_activation_unsupported` — the sentence this
// dialog showed for a year — rather than letting a user read a licence
// error for a licence that is fine. That mapping is what lets this build
// ship before the Worker does.
std::string licenseKey;
std::string email;
std::string password;
// The second step of the account route. Both are set together or neither
// is: a ticket with no chosen licence would ask the same question twice.
std::string selectionTicket;
std::string licenseId;
// ⚠ THE HASH, NEVER THE RAW MACHINE ID — see ICoreMachineId. The server
// hashes again with its own salt before it stores anything.
std::string machineIdHash;
// For the seat list on the portal: "Abdelrahman's MacBook", "macOS",
// "arm64", "1.0.0". A label is the user's own text and may be empty.
std::string machineLabel;
std::string osName;
std::string architecture;
std::string appVersion;
};
};
ICoreLicenseClient#
ICoreLicenseClient.h:118 · class · 4 declaration(s)
class ICoreLicenseClient {
public:
using Callback = std::function<void(const ICoreLicenseReply&)>;
virtual ~ICoreLicenseClient();
// POST /api/license/activate — claims a seat and returns the first token.
virtual void activate(const ICoreActivationRequest& request, Callback onReply) = 0;
// POST /api/license/refresh — the Worker verifies the signature and the
// jti and IGNORES exp, so an expired token is still refreshable. That is
// what makes offline grace recoverable.
virtual void refresh(const std::string& token, Callback onReply) = 0;
// POST /api/license/deactivate — frees THIS machine's seat.
//
// ⚠ TAKES THE ACTIVATION ID, NOT THE TOKEN, and it used to take the token.
// The Worker's handler reads `activation_id` and calls deactivate_activation
// with it (AccountsDataBase api-systemsicore/src/handlers.ts:23); a body
// carrying a token is refused 400 before anything is freed. The id is not a
// second credential to keep — it arrives inside the token as the `act.id`
// claim and is on ICoreVerifiedToken::activationId, so the gate reads it off
// the token it already verified.
virtual void deactivate(const std::string& activationId, Callback onReply) = 0;
};
};
ICoreHttpLicenseClient#
ICoreLicenseClient.h:146 · class · bases public ICoreLicenseClient · pImpl · nested Vector · 12 declaration(s)
The real one.
class ICoreHttpLicenseClient : public ICoreLicenseClient {
public:
ICoreHttpLicenseClient();
~ICoreHttpLicenseClient() override;
void activate(const ICoreActivationRequest& request, Callback onReply) override;
void refresh(const std::string& token, Callback onReply) override;
void deactivate(const std::string& activationId, Callback onReply) override;
// "https://systemsicore.com/api". A constant, exposed so a diagnostics
// line can print it — not so anything can change it.
[[nodiscard]] static std::string baseUrl();
// ---- the parts that can be wrong, exposed so they can be tested --------
//
// The transport cannot be unit-tested without a network, but the mapping
// and the request shaping can, and they are where the defects live. These
// are pure functions.
// `reachedServer` is false when no response line arrived at all.
[[nodiscard]] static ICoreLicenseReply parseReply(int httpStatus,
const std::string& body,
bool reachedServer);
// Whether an answer is the one a Worker WITHOUT the account deploy gives to
// a credentials body — which the client turns back into
// `email_activation_unsupported`, the sentence this dialog showed before
// the account route existed.
//
// ⚠ IT IS ONLY EVER ASKED ABOUT AN ACCOUNT REQUEST. The same 400 from a KEY
// activation means the client sent a broken body, which is a defect and not
// a missing feature; activate() knows which route it took and this function
// cannot, so it must not be consulted anywhere else.
[[nodiscard]] static bool isPreAccountWorker(const ICoreLicenseReply& reply);
[[nodiscard]] static std::string activationBody(const ICoreActivationRequest& request);
[[nodiscard]] static std::string tokenBody(const std::string& token);
[[nodiscard]] static std::string deactivationBody(const std::string& activationId);
struct Vector {
int httpStatus;
const char* body;
bool reachedServer;
ICoreLicenseReply::Outcome expected;
const char* pins;
};
[[nodiscard]] static std::vector<Vector> vectors();
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreFakeLicenseClient#
ICoreLicenseClient.h:205 · class · bases public ICoreLicenseClient · pImpl · 16 declaration(s)
The test one (A2.2).
class ICoreFakeLicenseClient : public ICoreLicenseClient {
public:
ICoreFakeLicenseClient();
~ICoreFakeLicenseClient() override;
void activate(const ICoreActivationRequest& request, Callback onReply) override;
void refresh(const std::string& token, Callback onReply) override;
void deactivate(const std::string& activationId, Callback onReply) override;
// What the next call of each kind answers. Set once; it stands until
// replaced, so a test that expects three refreshes to fail says so once.
void setActivateReply(const ICoreLicenseReply& reply);
void setRefreshReply(const ICoreLicenseReply& reply);
void setDeactivateReply(const ICoreLicenseReply& reply);
// Convenience for the two answers that matter most.
static ICoreLicenseReply ok(const std::string& token);
static ICoreLicenseReply refused(const std::string& errorCode, int httpStatus = 403);
static ICoreLicenseReply unreachable();
[[nodiscard]] int activateCount() const;
[[nodiscard]] int refreshCount() const;
[[nodiscard]] int deactivateCount() const;
// The token the last refresh/deactivate was given, and the last activation
// request — so a test can assert the RAW machine id never went out.
[[nodiscard]] std::string lastToken() const;
[[nodiscard]] ICoreActivationRequest lastActivation() const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreLicenseGate.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseGate.h
ICoreLicenseGate#
ICoreLicenseGate.h:62 · class · pImpl · 20 declaration(s)
ICoreLicenseGate — the one object that knows whether this copy is licensed.
class ICoreLicenseGate {
public:
using TimePoint = ICoreLicenseStore::TimePoint;
// How often a running application re-checks with the Worker. The token's
// own exp is 7 days; this is what learns about a revocation sooner.
static constexpr int REFRESH_EVERY_HOURS = 24;
// The environment variable the CI escape hatch reads. Named here so the
// provisioning documentation and the code cannot drift.
static constexpr const char* TOKEN_ENVIRONMENT_VARIABLE = "ICORE_LICENSE_TOKEN";
// ⚠ TWO STORES, AND BOTH ARE REQUIRED (A1.7). `vault` holds the token and
// nothing else; `records` holds the clock anchor, the machine id and the
// telemetry choice. resolveOnLaunch() writes the anchor as its FIRST
// statement, so pointing `records` at the OS vault puts a keychain write on
// the boot path -- which is the macOS authorisation prompt a headless run
// waits on forever and a signed-out user sees at startup for no reason.
// See the note at the top of ICoreLicenseStore.h.
ICoreLicenseGate(std::shared_ptr<ICoreTokenVerifier> verifier,
std::shared_ptr<ICoreLicenseClient> client,
std::shared_ptr<ICoreSecretVault> vault,
std::shared_ptr<ICoreRecordStore> records);
~ICoreLicenseGate();
ICoreLicenseGate(const ICoreLicenseGate&) = delete;
ICoreLicenseGate& operator=(const ICoreLicenseGate&) = delete;
// Fires whenever state() changes, never on a re-evaluation that lands on
// the same value. Receivers hold an ICoreSignalScope declared LAST.
ICoreSignal<ICoreLicenseState> stateChanged;
// ---- launch ----------------------------------------------------------
// Runs the resolution order above and evaluates the state. Does NOT touch
// the network: an offline launch with a valid stored token is Licensed
// immediately, and refresh happens after. Also writes the forward-only
// last-seen anchor.
void resolveOnLaunch(TimePoint now);
// TRUE when step 2 answered — this session is licensed by the MACHINE's
// provisioning and not by anything the person at the keyboard did.
//
// ⚠ SURFACE THIS WHEREVER YOU SHOW A LICENCE. It went unread by every
// caller in src/ until A3.4, and an environment-provisioned session was
// presented as an ordinary sign-in — so the account panel showed a licence
// for an account nobody had signed into, and there was no way to tell from
// the UI why signing out kept coming undone. ICoreAccountPanel says it now.
[[nodiscard]] bool resolvedFromEnvironment() const;
// ---- the lifecycle (A2.3) -------------------------------------------
using Completion = std::function<void(bool ok, const std::string& errorCode)>;
// POST /api/license/activate, then verify what came back and store it. A
// token that does not verify is NOT stored — that is the difference between
// an activation and a download.
void activate(const ICoreActivationRequest& request, TimePoint now, Completion onDone);
// Refresh now, whatever the schedule says. Called once on launch after
// resolution, and by "check now" in the account panel.
void refreshNow(TimePoint now, Completion onDone);
// Called from the shell's timer. Refreshes if 24 h have passed.
void tick(TimePoint now);
// POST /api/license/deactivate — frees THIS machine's seat, then signs out
// locally whatever the server said. A user who asked to release the seat
// must not be left holding a token if the request failed; the server
// reconciles on its own schedule.
void deactivateThisMachine(TimePoint now, Completion onDone);
// Local only: forgets the token without telling the server, so the seat
// stays claimed. This is "sign out", not "release this machine".
//
// ⚠ It also sets the sign-out tombstone, so it OUTRANKS the environment on
// the next launch. Both callers of the store's signOut() do — deactivating
// this machine is a sign-out too, and a seat released on the server while
// the next launch reads the token back out of the environment would be the
// same defect wearing a different button.
void signOut();
// ---- what everything else asks --------------------------------------
[[nodiscard]] ICoreLicenseState state() const;
[[nodiscard]] const ICoreEntitlements& entitlements() const;
[[nodiscard]] const ICoreVerifiedToken& token() const;
// ⚠ THE RAW BEARER JWT, AND THE ONLY REASON IT EXISTS IS THE DOWNLOAD
// GRANT. `token()` above returns the parsed claims, which is what every
// licence question should ask; this returns the credential itself, and
// anyone holding it holds the licence.
//
// Rules, and they are not style: never log it, never print it (console
// output lands in CI logs — see licenseStatus, which elides), never put it
// in telemetry, and never hand it to anything but ICoreDownloadClient's
// grant and -- the one other use, owner ruling 2026-10-03 -- the
// environment of a coding agent's check process (ICoreAgentSandbox), which
// is a --console copy of this app that can resolve a licence from
// TOKEN_ENVIRONMENT_VARIABLE only and removes it before running any model
// code. Empty when nothing resolved, which the caller must treat as "no
// download" rather than as an empty-string token to send.
[[nodiscard]] std::string rawToken() const;
[[nodiscard]] int graceDaysRemaining(TimePoint now) const;
[[nodiscard]] ICoreRefreshOutcome lastRefresh() const;
// The last refusal's code, for a dialog that has to say why.
[[nodiscard]] std::string lastErrorCode() const;
// ---- the account route's second step (2026-08-24) ---------------------
//
// Non-empty ONLY after an activation refused with `choose_license`, which
// is not a failure: the account holds more than one licence and the person
// picks which one this machine's seat comes off. Hand the chosen
// `ICoreLicenseChoice::id` back in a second ICoreActivationRequest together
// with pendingChoiceTicket(), and send no credentials with it.
//
// ⚠ CLEARED BY THE NEXT ACTIVATION, whatever it answers. A stale list is a
// dialog offering a choice the server is no longer asking for, and the
// ticket behind it expires in five minutes anyway.
[[nodiscard]] std::vector<ICoreLicenseChoice> pendingLicenseChoices() const;
// The proof that a password was checked a moment ago, so choosing does not
// ask for it again. ⚠ NOT A LICENCE and never stored: it carries a
// different audience, which ICoreJwtVerifier refuses.
[[nodiscard]] std::string pendingChoiceTicket() const;
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreLicenseKey.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseKey.h
ICoreLicenseKey#
ICoreLicenseKey.h:57 · class · nested Vector · 7 declaration(s)
ICoreLicenseKey — the offline check on ICB-XXXX-XXXX-XXXX-XXXX.
class ICoreLicenseKey {
public:
// The whole contract. Normalises, checks the prefix, the length and the
// check character. Never throws.
[[nodiscard]] static bool isValid(std::string_view key);
// Uppercased with every non-alphanumeric removed — what isValid() compares,
// mirroring the database's own validate_license_key().
//
// ⚠ NOT WHAT GOES ON THE WIRE, AND THIS COMMENT SAID IT WAS UNTIL
// 2026-08-24. `activate_license()` looks a key up with
// `where license_key = upper(p_license_key)` against a column whose values
// came from `generate_license_key()` — which groups them — so a
// separator-free key matches nothing and every real licence was answered
// `unknown_license`. formatted() is the wire form; see
// ICoreHttpLicenseClient::activationBody().
[[nodiscard]] static std::string normalized(std::string_view key);
// Regrouped as ICB-XXXX-XXXX-XXXX-XXXX — for display, AND for the wire:
// this is byte-for-byte what generate_license_key() stores, so it is what
// an activation must send. Returns the normalised text unchanged when it is
// not the expected length, because a formatter that pads or truncates hides
// the very error being reported.
[[nodiscard]] static std::string formatted(std::string_view key);
// The check character for 15 payload characters, or '\0' if any of them is
// outside the alphabet. Exposed because the vectors pin it directly.
[[nodiscard]] static char checkCharacter(std::string_view payload);
// 0123456789ABCDEFGHJKMNPQRSTVWXYZ.
[[nodiscard]] static std::string_view alphabet();
// ---- the agreement with Postgres ------------------------------------
struct Vector {
const char* key;
bool expected; // validate_license_key(key)
const char* pins; // the clause this case exists to pin
};
// The same table tools/license_key_vectors.sql runs through the database.
// Add a case here and add it there; the two lists are meant to be one list.
[[nodiscard]] static std::vector<Vector> vectors();
// Runs vectors() and appends a line per failure. Returns the failure count,
// so 0 is a pass. A7.5/A7.6 register this in ICorePreReleaseTests; until
// then it is callable from anywhere that wants the proof.
static int selfTest(std::vector<std::string>* failures);
};
ICoreLicenseState.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseState.h
ICoreFeature / ICoreLicenseState / ICoreEntitlements
The plain value types every gate in the tree reads. No network, no vault, no Qt, no I/O: what a licence SAYS, separated from how it was obtained, so a gate can be tested with a hand-built ICoreEntitlements and no token at all.
⚠ GATE ON A FEATURE, NEVER ON A TIER NAME.
tierId() == "commercial_team_sdk"compiles perfectly and is wrong: a licence carries per-licence feature_overrides, so the tier name does not determine the feature set, and a negotiated override is exactly the case a tier check gets wrong. It also passes its author's own test, because the author's licence is the tier they hardcoded. tierId() is here for display and diagnostics and for nothing else. The fixture that catches the mistake is a commercial_individual licence whose feature_overrides grant sdk_access: it must unlock the SDK.
ICoreEntitlements#
ICoreLicenseState.h:88 · class · pImpl · 24 declaration(s)
What one licence allows.
class ICoreEntitlements {
public:
ICoreEntitlements();
~ICoreEntitlements();
ICoreEntitlements(const ICoreEntitlements& other);
ICoreEntitlements& operator=(const ICoreEntitlements& other);
ICoreEntitlements(ICoreEntitlements&& other) noexcept;
ICoreEntitlements& operator=(ICoreEntitlements&& other) noexcept;
// ---- the gates ------------------------------------------------------
[[nodiscard]] bool has(ICoreFeature feature) const;
void setHas(ICoreFeature feature, bool allowed);
// "rust", "vhdl", "st", ... Matching is case-insensitive because a
// --console argument is typed by a human; the stored list is the server's.
// The code engine's display names are folded onto the claim's ids first --
// "C++" is "cpp", "System Verilog" is "systemverilog", "PLC - ST" is "st"
// -- because that is how ICoreCodeEngine asks (A4.7).
[[nodiscard]] bool allowsCodegenTarget(std::string_view target) const;
void setCodegenTargets(std::vector<std::string> targets);
[[nodiscard]] std::vector<std::string> codegenTargets() const;
// Read from the lic.commercial_use claim. NEVER inferred from the tier
// name, and never detected programmatically: the entitlement rests on the
// attestation signed at purchase, which is held server-side.
[[nodiscard]] bool allowsCommercialUse() const;
void setAllowsCommercialUse(bool allowed);
// ---- display and diagnostics ONLY -----------------------------------
[[nodiscard]] std::string tierId() const; // "commercial_individual"
void setTierId(std::string id);
[[nodiscard]] std::string tierDisplayName() const; // "Commercial — Individual"
void setTierDisplayName(std::string name);
[[nodiscard]] std::string plan() const; // billing fact
void setPlan(std::string plan);
[[nodiscard]] std::string billingPeriod() const; // billing fact
void setBillingPeriod(std::string period);
// ---- the updater's half ---------------------------------------------
// Empty means no ceiling and updates flow normally. Otherwise no release
// newer than this may be offered or installed — compared with the SAME
// semver comparison the server uses (ICoreSemVer, ported from semver_gt),
// and reported as "updates available with a maintenance plan" rather than
// as an error, because it is a healthy licensed state.
[[nodiscard]] std::string versionCeiling() const;
void setVersionCeiling(std::string version);
// True inside the post-purchase grace window: do not enforce the ceiling.
[[nodiscard]] bool inCeilingGrace() const;
void setInCeilingGrace(bool inGrace);
// ---- the claim vocabulary -------------------------------------------
// The JSON key this feature is spelled with in the token and in the
// database. The ONE place these strings exist.
[[nodiscard]] static std::string_view claimKey(ICoreFeature feature);
// The inverse. std::nullopt for a key this build does not know, which is
// NOT an error: the server may add a feature before the app learns it, and
// an unknown key must be ignored rather than fail the whole token.
[[nodiscard]] static std::optional<ICoreFeature> featureFromClaimKey(std::string_view key);
// Every enumerator, for a loop that must not miss one when the enum grows.
[[nodiscard]] static std::vector<ICoreFeature> allFeatures();
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
File-scope declarations#
// The gated capabilities. These are the eight the token carries as booleans;
// codegen targets are a list and live on their own accessor.
//
// ⚠ The public facade will put an icore::Feature in include/icore/License.h
// with the same eight enumerators. When it lands, pin the two together with
// static_asserts the way ICoreTextEnumsVerify.cpp pinned the Qt enums (deleted, LQ.13) — two enums
enum class ICoreFeature {
HdlExport,
SdkAccess,
SdkDownload,
CustomBlockAuthoring,
PluginLoad,
HeadlessCli,
Redistribution,
WatermarkGeneratedCode
};
// ⚠ GRACE IS NOT AN ERROR AND EXPIRED IS NOT HOSTILE.
//
// Unlicensed no valid token on this machine. Under the hard wall the editor
// window is never created; the sign-in dialog is what the user
// gets.
// Trial portal-issued, never minted in the app.
enum class ICoreLicenseState {
Unlicensed,
Trial,
Licensed,
Grace,
Expired
};
ICoreLicenseStateMachine.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseStateMachine.h
ICoreLicenseStateMachine — which of the five states this copy is in.
A PURE FUNCTION OF FIVE FACTS, and that is the whole design: it takes the token's expiry, the vault's last-seen anchor, the system clock, whether the licence is a trial, and how the last refresh went. It reads no vault, makes no request, parses no token and consults no clock of its own, so every rule below is assertable in a unit test with no network, no keychain and no sleeping — which is what A7.2 does with a table of dated cases.
⚠ THE THREE RULES THAT ARE EASY TO GET BACKWARDS.
- A REFUSED REFRESH IS EXPIRED, NOT GRACE. Grace exists for a Worker that
could not be REACHED. A Worker that answered "revoked" was reached, and honouring 30 more days of grace after a revocation would make revocation
ICoreLicenseStateMachine#
ICoreLicenseStateMachine.h:53 · class · nested Input, Vector · 5 declaration(s)
class ICoreLicenseStateMachine {
public:
using TimePoint = std::chrono::system_clock::time_point;
// Everything the decision depends on. A struct rather than eight arguments
// because a caller that swaps two time points compiles perfectly.
struct Input {
bool hasToken = false;
bool isTrial = false;
TimePoint tokenExpiry = TimePoint{}; // the verified `exp`
TimePoint lastSeenUtc = TimePoint{}; // the vault's anchor
TimePoint systemNow = TimePoint{}; // the machine's clock
ICoreRefreshOutcome lastRefresh = ICoreRefreshOutcome::NotAttempted;
};
// 30 days, from the token's expiry. Not from the first failed refresh: a
// user who never launches for a month must not silently reset the window.
static constexpr std::chrono::hours GRACE_WINDOW{24 * 30};
[[nodiscard]] static ICoreLicenseState evaluate(const Input& in);
// max(systemNow, lastSeenUtc) — see rule 3.
[[nodiscard]] static TimePoint effectiveNow(const Input& in);
// Whole days left before GRACE becomes EXPIRED, rounded UP so the last
// partial day still reads as "1 day left" rather than "0". 0 in every other
// state, and never negative.
[[nodiscard]] static int graceDaysRemaining(const Input& in);
// ---- the case table -------------------------------------------------
struct Vector {
const char* name;
bool hasToken;
bool isTrial;
int expiryDaysFromBase; // relative to a fixed base
int lastSeenDaysFromBase;
int systemNowDaysFromBase; // may be BEFORE last seen
ICoreRefreshOutcome lastRefresh;
ICoreLicenseState expected;
int expectedGraceDays; // -1 = do not assert
const char* pins;
};
[[nodiscard]] static std::vector<Vector> vectors();
// Runs vectors() and appends a line per failure. 0 is a pass. A7.6 registers
// this in ICorePreReleaseTests; until then it is callable from anywhere.
static int selfTest(std::vector<std::string>* failures);
};
File-scope declarations#
// How the last attempt to refresh the token went.
enum class ICoreRefreshOutcome {
NotAttempted, // nothing has been tried yet this run
Ok, // a fresh token came back
Unreachable, // no answer: no network, DNS, timeout, connection refused
Refused // the Worker ANSWERED and said no — revoked, unknown jti
};
ICoreLicenseStore.h#
src/ICoreBlocks/ICoreAccount/ICoreLicenseStore.h
⚠ <filesystem> IS THIS HEADER'S OWN, AND IT WAS BORROWED UNTIL W10.33 (2026-09-13). ICoreFileRecordStore's constructor and defaultPath() both spell std::filesystem::path, and nothing here declared it -- every .cpp that included this header happened to reach <filesystem> first through something else.
sdk_qt_free_selftest, which compiles the SDK's header graph with a bare include set and nothing else, is what asks the question honestly, and it could not ask it until the Qt compatibility seam came off this configuration: it failed on <QString> before it ever reached this line.📌 A header that compiles everywhere it is included has not been shown to be self-contained -- only that nobody has included it first.
ICoreSecretVault#
ICoreLicenseStore.h:78 · class · 6 declaration(s)
One secret store, keyed by account name within one service.
class ICoreSecretVault {
public:
virtual ~ICoreSecretVault();
// Empty when absent OR unreadable — the two are not distinguished, because
// the caller's response to both is the same: ask the user again.
[[nodiscard]] virtual std::string read(const std::string& account) const = 0;
virtual bool write(const std::string& account, const std::string& value) = 0;
virtual bool erase(const std::string& account) = 0;
// False when the backing store is not a real vault. Surface it.
[[nodiscard]] virtual bool isSecure() const = 0;
[[nodiscard]] virtual std::string backingStoreName() const = 0;
};
};
ICoreOsSecretVault#
ICoreLicenseStore.h:96 · class · bases public ICoreSecretVault · pImpl · 8 declaration(s)
The real one: ICoreCredentialStore, under this product's account service.
class ICoreOsSecretVault : public ICoreSecretVault {
public:
ICoreOsSecretVault();
~ICoreOsSecretVault() override;
[[nodiscard]] std::string read(const std::string& account) const override;
bool write(const std::string& account, const std::string& value) override;
bool erase(const std::string& account) override;
[[nodiscard]] bool isSecure() const override;
[[nodiscard]] std::string backingStoreName() const override;
// "<product> Account". Separate from the copilot's service so signing out
// of one cannot disturb the other.
[[nodiscard]] static std::string serviceName();
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreMemorySecretVault#
ICoreLicenseStore.h:119 · class · bases public ICoreSecretVault · pImpl · 10 declaration(s)
The test one.
class ICoreMemorySecretVault : public ICoreSecretVault {
public:
ICoreMemorySecretVault();
~ICoreMemorySecretVault() override;
[[nodiscard]] std::string read(const std::string& account) const override;
bool write(const std::string& account, const std::string& value) override;
bool erase(const std::string& account) override;
[[nodiscard]] bool isSecure() const override;
[[nodiscard]] std::string backingStoreName() const override;
// Makes every write fail, for the path where the vault refuses. A store
// that silently believed a failed write would report a licence the next
// launch cannot find.
void setWritesFail(bool fail);
// How many reads reached the vault — the lazy-caching promise is only worth
// stating if a test can hold it to it.
[[nodiscard]] int readCount() const;
// How many writes reached the vault. ⚠ Counted for one specific assertion:
// the boot path must never WRITE here. A write is what turns an absent
// keychain item into a present one, and a present item is what the macOS
// ACL prompt is raised about — so a boot path that writes puts that dialog
// in front of a user who has no secret stored at all (A1.7).
[[nodiscard]] int writeCount() const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreRecordStore#
ICoreLicenseStore.h:161 · class · 5 declaration(s)
The records: everything that is NOT a secret.
class ICoreRecordStore {
public:
virtual ~ICoreRecordStore();
// Empty when absent, unreadable, OR failing its integrity digest — the
// three are not distinguished, because the caller's response to all of them
// is the same: behave as though this installation had never recorded one.
[[nodiscard]] virtual std::string read(const std::string& key) const = 0;
virtual bool write(const std::string& key, const std::string& value) = 0;
virtual bool erase(const std::string& key) = 0;
// For a diagnostic line, not for a decision.
[[nodiscard]] virtual std::string backingStoreName() const = 0;
};
};
ICoreFileRecordStore#
ICoreLicenseStore.h:180 · class · bases public ICoreRecordStore · pImpl · 7 declaration(s)
The real one: one 0600 file, rewritten whole.
class ICoreFileRecordStore : public ICoreRecordStore {
public:
// NOT defaulted to defaultPath(): as with osVault(), constructing this must
// be a place where someone had to TYPE which file they meant — a test that
// gets the real one by omission writes into the user's application data
// folder and takes their clock anchor with it.
explicit ICoreFileRecordStore(std::filesystem::path file);
~ICoreFileRecordStore() override;
[[nodiscard]] std::string read(const std::string& key) const override;
bool write(const std::string& key, const std::string& value) override;
bool erase(const std::string& key) override;
[[nodiscard]] std::string backingStoreName() const override;
// <appDataLocation(product)>/account.state — the same folder
// ICoreDocumentsFolder establishes as the application home, derived the same
// way rather than asked for, because that class is at L9 and this is at L2.
// Empty when the OS cannot say where application data goes, in which case
// reads answer empty and writes fail — the store's existing "a refused write
// is never cached" path, and nothing new to handle.
[[nodiscard]] static std::filesystem::path defaultPath();
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreMemoryRecordStore#
ICoreLicenseStore.h:209 · class · bases public ICoreRecordStore · pImpl · 7 declaration(s)
The test one, and the headless one.
class ICoreMemoryRecordStore : public ICoreRecordStore {
public:
ICoreMemoryRecordStore();
~ICoreMemoryRecordStore() override;
[[nodiscard]] std::string read(const std::string& key) const override;
bool write(const std::string& key, const std::string& value) override;
bool erase(const std::string& key) override;
[[nodiscard]] std::string backingStoreName() const override;
// Makes every write fail, for the path where the store refuses.
void setWritesFail(bool fail);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreLicenseStore#
ICoreLicenseStore.h:228 · class · pImpl · 18 declaration(s)
class ICoreLicenseStore {
public:
using TimePoint = std::chrono::system_clock::time_point;
// ⚠ BOTH ARE REQUIRED, and neither is defaulted. A one-argument form that
// sent the records wherever the secrets went is exactly the arrangement
// A1.7 removed, and it would come back silently: everything would still
// work, and the keychain prompt would be back at startup.
ICoreLicenseStore(std::shared_ptr<ICoreSecretVault> vault,
std::shared_ptr<ICoreRecordStore> records);
~ICoreLicenseStore();
ICoreLicenseStore(const ICoreLicenseStore&) = delete;
ICoreLicenseStore& operator=(const ICoreLicenseStore&) = delete;
// The two stores production uses. Not default arguments of the constructor:
// constructing this class must be a place where someone had to TYPE which
// pair they meant.
[[nodiscard]] static std::shared_ptr<ICoreSecretVault> osVault();
[[nodiscard]] static std::shared_ptr<ICoreRecordStore> osRecordStore();
// ---- the token ------------------------------------------------------
[[nodiscard]] std::string token() const; // "" when there is none
bool setToken(const std::string& token);
bool clearToken();
// ---- the clock anchor -----------------------------------------------
// The last moment this installation is known to have run. Epoch when the
// record store holds nothing, holds something unparseable, or holds a row
// whose integrity digest does not match — see the note at the top.
[[nodiscard]] TimePoint lastSeenUtc() const;
// FORWARD ONLY. An earlier value is dropped rather than written, so the
// anchor cannot be walked backwards by launching with the clock set back —
// which is the whole mechanism ICoreLicenseStateMachine rule 3 rests on.
// Returns true when the anchor moved.
bool noteSeen(TimePoint now);
// ---- this machine ---------------------------------------------------
// The machine id the stored token was activated with. When it differs from
// ICoreMachineId::value() today, the account state was restored onto another
// machine and the token belongs to a seat this machine does not hold.
//
// ⚠ It is a FINGERPRINT, not a secret, so it is a record — and the record
// file travels differently from the keychain (a migrated home folder carries
// it; Migration Assistant carries both). Absence reads as "never activated",
// which is the safe answer either way.
[[nodiscard]] std::string machineId() const;
bool setMachineId(const std::string& id);
// ---- telemetry ------------------------------------------------------
// DEFAULT ON, mirroring profiles.telemetry_opt_in. Absent means on: a user
// who has never been asked has not opted out.
[[nodiscard]] bool telemetryOptIn() const;
bool setTelemetryOptIn(bool optIn);
// ---- lifecycle ------------------------------------------------------
// Sign-out. Clears the token, the machine id and the anchor, and SETS the
// tombstone below — but NOT the telemetry choice, which is a preference the
// user set and not part of the licence. ⚠ Clearing the anchor is deliberate and safe: it is only ever
// used to stop a clock going backwards within one licence's grace, and
// there is no licence any more.
bool signOut();
// ⚠ THE SIGN-OUT TOMBSTONE (A3.4, 2026-08-24). Sign-out clears what is
// STORED, and until this row existed that was the whole of it -- so on a
// machine provisioned with ICORE_LICENSE_TOKEN (A3.3) the next launch
// resolved step 2 and signed the user straight back in, after they had
// watched the panel empty and the banner say "Signed out". Measured: with
// an empty vault and no account.state, the same binary answers Licensed
// with the variable set and Unlicensed without it.
//
// This row is what makes a sign-out outrank the machine's provisioning:
// ICoreLicenseGate::resolveOnLaunch() SKIPS STEP 2 while it is set. Nothing
// else about the resolution order changes.
//
// ⚠ IT SUPPRESSES THE ENVIRONMENT AND NEVER THE VAULT. A vault token got
// there by someone signing in ON THIS MACHINE, which outranks any earlier
// sign-out; step 1 clears the row when it adopts one, and setToken() clears
// it too, so a stored token and a live tombstone can never coexist.
//
// ⚠ IT CANNOT REACH A CONSOLE SESSION, which is why making sign-out durable
// cost CI nothing. Initialization::resolveLicense() hands a console session
// an ICoreMemoryRecordStore, so a headless run reads no account.state at
// all and goes on resolving ICORE_LICENSE_TOKEN exactly as A3.3 requires --
// a GUI sign-out cannot take the pre-release machine's suites down with it.
// ⚠ Do not "simplify" that by giving console sessions the real record
// store: it would put a user's sign-out in the path of every suite.
[[nodiscard]] bool signedOutHere() const;
// Signing back in. Called by setToken() and by resolveOnLaunch() step 1, so
// no caller has to remember the pairing.
bool clearSignedOut();
// ⚠ BOTH DESCRIBE THE TOKEN'S STORE, not the record file. The account panel
// shows them next to the licence, and "local file (no OS vault available)"
// must keep meaning "your licence token is not in a vault" — never "your
// clock anchor is in a text file", which is true everywhere and alarms
// nobody usefully.
[[nodiscard]] bool isSecure() const;
[[nodiscard]] std::string backingStoreName() const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreTelemetryEvent.h#
src/ICoreBlocks/ICoreAccount/ICoreTelemetryEvent.h
ICoreTelemetryEvent#
ICoreTelemetryEvent.h:119 · class · pImpl · nested Vector · 32 declaration(s)
class ICoreTelemetryEvent {
public:
using TimePoint = std::chrono::system_clock::time_point;
ICoreTelemetryEvent();
~ICoreTelemetryEvent();
ICoreTelemetryEvent(const ICoreTelemetryEvent& other);
ICoreTelemetryEvent& operator=(const ICoreTelemetryEvent& other);
ICoreTelemetryEvent(ICoreTelemetryEvent&& other) noexcept;
ICoreTelemetryEvent& operator=(ICoreTelemetryEvent&& other) noexcept;
// ---- the factories, and there is no other way to build one -----------
static ICoreTelemetryEvent modelOpen(TimePoint at);
static ICoreTelemetryEvent modelSave(TimePoint at);
static ICoreTelemetryEvent simulateRun(TimePoint at);
static ICoreTelemetryEvent pluginLoad(TimePoint at);
static ICoreTelemetryEvent sdkCall(TimePoint at);
// "rust", "vhdl", "st", ... — one of the nine the database documents.
static ICoreTelemetryEvent codegenRun(std::string_view target, TimePoint at);
// ⚠ THE BLOCK TYPE, NEVER THE BLOCK'S NAME. "Control_Systems/Base_Blocks/Gain"
// is a catalog identifier and is fine; "Roll Rate Gain" is what the user
// called their instance and is not.
static ICoreTelemetryEvent blockAdded(std::string_view blockType, TimePoint at);
// What kind of thing was exported ("simulink", "recipe", "hdl"), never
// where it went.
static ICoreTelemetryEvent exported(std::string_view what, TimePoint at);
static ICoreTelemetryEvent licenseCheck(ICoreLicenseState state, TimePoint at);
static ICoreTelemetryEvent featureBlocked(ICoreFeature feature, TimePoint at);
// ---- the updater (U7.1) ----------------------------------------------
// Counts and categories. updateApplied() carries no version on purpose:
// it would be the one field here that identified WHICH build a machine is
// on, and the server already knows that from the licence refresh.
static ICoreTelemetryEvent updateCheck(ICoreUpdateCheckResult result, TimePoint at);
static ICoreTelemetryEvent updateDownload(TimePoint at);
static ICoreTelemetryEvent updateApplied(TimePoint at);
static ICoreTelemetryEvent updateFailed(ICoreUpdateFailure reason, TimePoint at);
// ---- reading one -----------------------------------------------------
[[nodiscard]] ICoreTelemetryEventKind kind() const;
[[nodiscard]] std::string typeName() const; // the database's spelling
[[nodiscard]] std::string target() const; // "" unless codegen_run
[[nodiscard]] std::string detail() const; // the one property, or ""
[[nodiscard]] std::string detailKey() const; // its name in properties
[[nodiscard]] TimePoint occurredAt() const;
// The wire and on-disk shape, identical so a persisted queue posts exactly
// what it saved. { "event_type", "target"?, "properties"?, "occurred_at" }.
[[nodiscard]] std::string toJson() const;
// Round-trips toJson(). Returns false for anything it does not recognise —
// a persisted queue from a newer build must not crash an older one.
static bool fromJson(const std::string& json, ICoreTelemetryEvent* out);
// ---- the guarantee ---------------------------------------------------
// True when `token` is safe to send: see the header comment for the four
// rules. Public because it is the promise this class makes, and a promise
// nobody can test is not one.
[[nodiscard]] static bool isSafeToken(std::string_view token);
// What an unsafe token becomes.
[[nodiscard]] static std::string_view unsafePlaceholder();
[[nodiscard]] static std::string_view typeNameFor(ICoreTelemetryEventKind kind);
// Every kind, for a loop that must not miss one when the enum grows.
// fromJson() and selfTest() both walked `0 .. FeatureBlocked` by hand and
// both would have silently stopped recognising anything added after it.
[[nodiscard]] static std::vector<ICoreTelemetryEventKind> allKinds();
// The property vocabularies, spelled once. A dashboard groups on these.
[[nodiscard]] static std::string_view checkResultToken(ICoreUpdateCheckResult result);
[[nodiscard]] static std::string_view failureToken(ICoreUpdateFailure reason);
struct Vector {
const char* token;
bool safe;
const char* pins;
};
[[nodiscard]] static std::vector<Vector> vectors();
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
File-scope declarations#
// ICoreTelemetryEvent — one thing that happened, in a shape that CANNOT carry
// what it must not carry.
//
// ⚠ COUNTS AND TYPE NAMES ONLY. Never model contents, never parameter values,
// never file paths, never file names, never project names, never anything the
// user typed. That is customer engineering IP and a leak is a P0 — a
enum class ICoreTelemetryEventKind {
ModelOpen, // model_open
ModelSave, // model_save
SimulateRun, // simulate_run
CodegenRun, // codegen_run — carries the target
BlockAdded, // block_added — carries the block TYPE, never its name
Export, // export
PluginLoad, // plugin_load
SdkCall, // sdk_call
LicenseCheck, // license_check
FeatureBlocked, // feature_blocked — the one that says which wall people hit
// ---- the updater's four (U7.1) ---------------------------------------
//
// usage_events.event_type is `text not null` with NO enum and NO check
// constraint — the ten above are a COMMENT on the column, not a domain —
// so these four need no migration and record_usage_events accepts them as
// they stand. What they DO need is that comment extended, and that belongs
// in AccountsDataBase/20260819_updater.sql beside the rest of the updater
// proposal: a value the column comment does not list is a value nobody
// writing a dashboard knows exists.
UpdateCheck, // update_check — carries what the check concluded
UpdateDownload, // update_download
UpdateApplied, // update_applied
UpdateFailed // update_failed — carries WHY, from a closed vocabulary
};
// What an update check concluded, as telemetry sees it.
//
// ⚠ THIS IS NOT ICoreUpdateChecker::Outcome AND IT DELIBERATELY CANNOT BE.
// The checker is L3 and this module is L2, so the vocabulary cannot be shared
// downward without inverting the layering. The ONE translation between the two
// lives in ICoreUpdater/ICoreUpdateTelemetry.h, which can see both, and the
enum class ICoreUpdateCheckResult {
UpToDate,
UpdateAvailable,
HeldByCeiling, // a healthy licensed state, NOT a failure
HeldByRollout,
// U2.6. Both are "there IS an update and this machine cannot take it
// yet", which is neither up-to-date nor a failure. They are separate
// categories because they need different answers from us: a spike in
// held_by_os_version says a floor was set too high, a spike in
// held_by_upgrade_path says an intermediate release is not reaching
// people.
HeldByOsVersion,
HeldByUpgradePath,
RunningYanked,
Failed // the check itself did not complete
};
// Why an update did not happen. Same layering note as above; the mapping from
// ICoreDownloadDenial::Reason lives with the updater.
//
// ⚠ Every one of these is a CATEGORY, never a detail. No path, no host, no
// version, no message from a server. The event says "a digest did not match",
// and the log — which stays on the machine — says which file.
enum class ICoreUpdateFailure {
CheckFailed,
Denied, // the Worker refused the grant
DownloadFailed,
DigestMismatch,
SignatureInvalid,
PackageManaged, // U5.4: this install is not ours to replace
StagingFailed,
ApplyFailed
};
ICoreTelemetryPreference.h#
src/ICoreBlocks/ICoreAccount/ICoreTelemetryPreference.h
ICoreTelemetryPreference#
ICoreTelemetryPreference.h:36 · class · 5 declaration(s)
ICoreTelemetryPreference — the one answer to "may this copy report usage", and the seam the two places that show the switch both read.
class ICoreTelemetryPreference {
public:
ICoreTelemetryPreference() = delete;
// DEFAULT ON, mirroring profiles.telemetry_opt_in. A user who has never
// been asked has not opted out.
[[nodiscard]] static bool optIn();
// Writes through to the vault and to the running session at once. Turning
// it OFF discards whatever the session has queued — the user asked for it
// not to be sent, and "already collected" is not an exception to that.
static void setOptIn(bool optIn);
// Whether a live account manager is behind the value. The account panel
// says so plainly rather than implying a guarantee that is not there.
[[nodiscard]] static bool isBound();
// Fires on every change, whoever made it, so both switches agree without
// either polling. Receivers hold an ICoreSignalScope declared LAST.
[[nodiscard]] static ICoreSignal<bool>& onChanged();
};
};
ICoreTelemetryQueue.h#
src/ICoreBlocks/ICoreAccount/ICoreTelemetryQueue.h
ICoreTelemetryQueue#
ICoreTelemetryQueue.h:31 · class · pImpl · 17 declaration(s)
ICoreTelemetryQueue — events waiting to be posted, bounded, and persisted across restarts.
class ICoreTelemetryQueue {
public:
using TimePoint = ICoreTelemetryEvent::TimePoint;
// The four numbers the design fixes. Public because a test that hard-codes
// 1000 in three places stops meaning anything when the number moves.
static constexpr std::size_t CAPACITY = 1000;
static constexpr std::size_t FLUSH_AT_COUNT = 100;
static constexpr int FLUSH_EVERY_SECONDS = 60;
static constexpr std::size_t MAX_PER_POST = 500;
ICoreTelemetryQueue();
~ICoreTelemetryQueue();
ICoreTelemetryQueue(const ICoreTelemetryQueue&) = delete;
ICoreTelemetryQueue& operator=(const ICoreTelemetryQueue&) = delete;
void add(const ICoreTelemetryEvent& event);
[[nodiscard]] std::size_t size() const;
[[nodiscard]] bool isEmpty() const;
// How many events this queue has thrown away for the bound, ever. Worth
// surfacing in diagnostics: a machine dropping thousands is a machine whose
// flushes are not landing.
[[nodiscard]] std::size_t droppedCount() const;
// 100 events, or 60 seconds since the last flush with anything waiting.
[[nodiscard]] bool shouldFlush(TimePoint now) const;
// Removes and returns up to MAX_PER_POST events, oldest first.
[[nodiscard]] std::vector<ICoreTelemetryEvent> takeBatch();
// The flush landed. Restarts the 60-second clock.
void noteFlushed(TimePoint now);
// The flush did NOT land: the events go back at the FRONT, because they are
// the oldest. If that overflows the bound the oldest are dropped, which may
// include the ones just returned — correct, and the alternative (dropping
// what happened since) throws away the newer information to keep the older.
void returnBatch(const std::vector<ICoreTelemetryEvent>& batch);
// ---- persistence ----------------------------------------------------
//
// One event per line, each line exactly what toJson() produced, so a file
// written by one build is readable by the next and a truncated last line
// costs one event rather than the file.
[[nodiscard]] std::string toJsonLines() const;
// Replaces the contents. Lines that do not parse are SKIPPED and counted as
// drops — a half-written file after a crash must not lose the whole queue.
void loadJsonLines(const std::string& text);
// Convenience over the two above; both are no-ops on an unreadable or
// unwritable path, because telemetry never fails the thing it measures.
bool save(const std::string& path) const;
bool load(const std::string& path);
// The POST body: {"session_id": ..., "events": [ ... ]}.
[[nodiscard]] static std::string batchBody(const std::vector<ICoreTelemetryEvent>& batch,
const std::string& sessionId);
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreTelemetrySession.h#
src/ICoreBlocks/ICoreAccount/ICoreTelemetrySession.h
ICoreTelemetryClient#
ICoreTelemetrySession.h:39 · class · 3 declaration(s)
ICoreTelemetryClient / ICoreTelemetrySession — one run of the application, reported.
class ICoreTelemetryClient {
public:
// Delivery is fire-and-forget as far as the caller is concerned; `ok` says
// whether the batch may be discarded or has to go back on the queue.
using Callback = std::function<void(bool ok)>;
virtual ~ICoreTelemetryClient();
// POST /api/telemetry/session → record_session
virtual void postSession(const std::string& body, Callback onDone) = 0;
// POST /api/telemetry/events → record_usage_events
virtual void postEvents(const std::string& body, Callback onDone) = 0;
};
};
ICoreHttpTelemetryClient#
ICoreTelemetrySession.h:56 · class · bases public ICoreTelemetryClient · pImpl · 5 declaration(s)
The real one, over ICoreHttpClient.
class ICoreHttpTelemetryClient : public ICoreTelemetryClient {
public:
ICoreHttpTelemetryClient();
~ICoreHttpTelemetryClient() override;
void postSession(const std::string& body, Callback onDone) override;
void postEvents(const std::string& body, Callback onDone) override;
// Who the session belongs to (A6.6). Asked on EVERY post, not once: the
// licence token changes under a running session -- sign-in from the wall,
// the daily refresh, sign-out -- and a token captured at launch would file
// the rest of the run under whoever was signed in at the first heartbeat.
// The server takes the user, licence and activation from this verified
// token and never from the body. Empty, or no source, sends no header and
// the session is recorded without an account.
void setBearerTokenSource(std::function<std::string()> source);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreFakeTelemetryClient#
ICoreTelemetrySession.h:80 · class · bases public ICoreTelemetryClient · pImpl · 8 declaration(s)
The test one: records the bodies, answers from a flag, sends nothing.
class ICoreFakeTelemetryClient : public ICoreTelemetryClient {
public:
ICoreFakeTelemetryClient();
~ICoreFakeTelemetryClient() override;
void postSession(const std::string& body, Callback onDone) override;
void postEvents(const std::string& body, Callback onDone) override;
void setSucceeds(bool succeeds);
[[nodiscard]] std::vector<std::string> sessionBodies() const;
[[nodiscard]] std::vector<std::string> eventBodies() const;
[[nodiscard]] int postCount() const;
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreTelemetrySession#
ICoreTelemetrySession.h:100 · class · pImpl · 22 declaration(s)
class ICoreTelemetrySession {
public:
using TimePoint = ICoreTelemetryQueue::TimePoint;
static constexpr int HEARTBEAT_EVERY_SECONDS = 60;
ICoreTelemetrySession(std::shared_ptr<ICoreTelemetryClient> client, std::string clientSessionId);
~ICoreTelemetrySession();
ICoreTelemetrySession(const ICoreTelemetrySession&) = delete;
ICoreTelemetrySession& operator=(const ICoreTelemetrySession&) = delete;
// A fresh version-4 UUID. One per launch; it is the idempotency key
// app_sessions is unique on, so re-sending a heartbeat can never create a
// second session row.
[[nodiscard]] static std::string newClientSessionId();
[[nodiscard]] std::string clientSessionId() const;
// ---- the switch ------------------------------------------------------
// Default ON, mirroring profiles.telemetry_opt_in. Turning it OFF mid-run
// discards whatever is queued: the user asked for it not to be sent, and
// "already collected" is not an exception to that.
void setOptedIn(bool optedIn);
[[nodiscard]] bool isOptedIn() const;
// Where the queue survives a restart. Unset (the default) means memory only.
void setPersistencePath(const std::string& path);
// ---- the run ---------------------------------------------------------
void start(TimePoint now);
void record(const ICoreTelemetryEvent& event, TimePoint now);
void noteInteraction(TimePoint now);
void setFocused(bool focused, TimePoint now);
// Called from the shell's timer. Sends a heartbeat when one is due and
// flushes the queue when it asks to be flushed. Cheap when neither is.
void tick(TimePoint now);
// "clean_exit". Sends the final session row and flushes what is queued.
void end(TimePoint now);
// ---- what it would send ---------------------------------------------
[[nodiscard]] std::string sessionBody(TimePoint now, const std::string& endReason) const;
[[nodiscard]] int activeSeconds(TimePoint now) const;
[[nodiscard]] std::size_t queuedCount() const;
// The machine facts app_sessions carries. Optional: unset fields are
// omitted rather than sent empty.
void setAppVersion(const std::string& version);
void setPlatform(const std::string& osName, const std::string& architecture);
void setMachineFingerprint(const std::string& hash); // the HASH, never the raw id
void setOfflineGrace(bool inGrace);
static int selfTest(std::vector<std::string>* failures);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};
ICoreTokenVerifier.h#
src/ICoreBlocks/ICoreAccount/ICoreTokenVerifier.h
ICoreTokenVerifier — the seam every licence decision passes through.
THE PRODUCTION IMPLEMENTATION IS ICoreJwtVerifier (A1.4, landed 2026-08-19), over the vendored libsodium behind ICoreSignatureVerifier. The other implementation in the tree is ICoreFakeTokenVerifier, which is test-only and says so loudly.
⚠ THE WALL IS STILL CLOSED, AND FOR A DIFFERENT REASON NOW. It used to be closed because no real verifier existed. It is closed today because ICoreJwtVerifier::pinnedKeys() is EMPTY -- the JWT signing key's public half has not been published into this repository -- so every token is refused with "untrusted_kid". Read that function's comment before adding a key: a placeholder key is a key somebody has the private half of, and a key fetched over the network is a key an attacker who owns the network chooses.
ICoreVerifiedToken#
ICoreTokenVerifier.h:42 · struct · 0 declaration(s)
The claims, after the signature has been checked.
struct ICoreVerifiedToken {
public:
using TimePoint = std::chrono::system_clock::time_point;
bool valid = false;
std::string errorCode; // "bad_signature", "untrusted_kid", "malformed",
// "wrong_audience"
//
// ⚠ "not_yet_valid" used to be listed here and
// NO VERIFIER RETURNS IT. A verifier holds no
// clock, deliberately -- see ICoreJwtVerifier.h.
// `nbf` is carried below, not enforced.
// Identity (sub / the profile behind it).
std::string subject;
std::string email;
// The licence.
std::string licenseId;
std::string tierId; // display and diagnostics ONLY
std::string tierDisplayName;
std::string plan; // a billing fact
std::string billingPeriod; // a billing fact
bool isTrial = false;
ICoreEntitlements entitlements;
// This machine's seat.
std::string activationId;
std::string fingerprint; // the HASH the server stored
TimePoint issuedAt; // `iat`
TimePoint expiresAt; // the 7-day `exp`
// `nbf`. CARRIED, NOT ENFORCED, and nothing reads it yet.
//
// The verifier will not enforce it, for the reason in ICoreJwtVerifier.h: a
// class that reads the clock gives different answers for the same input, and
// a machine whose clock is wrong would be reported as holding a bad
// signature. The one class allowed an opinion about time is
// ICoreLicenseStateMachine, and it does not consume this field today -- it
// has never needed to, because the Worker mints nbf == iat. If a future
// token ever carries a future nbf, that is the class to teach, and this
// comment is the note saying so.
TimePoint notBefore;
};
};
ICoreTokenVerifier#
ICoreTokenVerifier.h:87 · class · 2 declaration(s)
class ICoreTokenVerifier {
public:
virtual ~ICoreTokenVerifier();
// Verifies the signature against a PINNED public key by `kid`, then parses.
// MUST return valid == false for anything it cannot verify — a token whose
// key it does not know, whose signature does not match, whose audience is
// somebody else's, or which is not a token at all.
[[nodiscard]] virtual ICoreVerifiedToken verify(const std::string& token) const = 0;
};
};
ICoreFakeTokenVerifier#
ICoreTokenVerifier.h:108 · class · bases public ICoreTokenVerifier · pImpl · 8 declaration(s)
⚠ TEST ONLY, AND IT VERIFIES NOTHING.
class ICoreFakeTokenVerifier : public ICoreTokenVerifier {
public:
ICoreFakeTokenVerifier();
~ICoreFakeTokenVerifier() override;
[[nodiscard]] ICoreVerifiedToken verify(const std::string& token) const override;
// Every token verifies to this, unless it is one of the refusals below.
void setResult(const ICoreVerifiedToken& result);
// Exact token texts that must come back invalid, with a reason.
void refuse(const std::string& token, const std::string& errorCode);
[[nodiscard]] int verifyCount() const;
[[nodiscard]] std::string lastToken() const;
// A licensed token expiring at `expiresAt`, with the SDK feature and the
// usual codegen targets. The shape most cases want.
[[nodiscard]] static ICoreVerifiedToken licensed(ICoreVerifiedToken::TimePoint expiresAt);
private:
class Impl; // the two-line residue; state lives here
std::unique_ptr<Impl> impl;
};