Skip to content

Commit ff055c4

Browse files
committed
feat(project): add offline metadata storage semantics
1 parent 7b0addd commit ff055c4

4 files changed

Lines changed: 1024 additions & 0 deletions

File tree

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/** @file MetadataStorage.hpp
2+
* Semantic trusted-state storage and disposable package metadata cache.
3+
*/
4+
#ifndef VIX_CLI_PROJECT_NATIVE_METADATA_STORAGE_HPP
5+
#define VIX_CLI_PROJECT_NATIVE_METADATA_STORAGE_HPP
6+
7+
#include <optional>
8+
#include <string>
9+
#include <string_view>
10+
#include <vector>
11+
12+
#include <vix/cli/project/native/RegistrySnapshotState.hpp>
13+
14+
namespace vix::cli::project::native
15+
{
16+
enum class TrustedStateStoreErrorCode { trusted_state_concurrent_update, invalid_trusted_snapshot_state };
17+
struct TrustedStateStoreIssue
18+
{
19+
TrustedStateStoreErrorCode code{TrustedStateStoreErrorCode::invalid_trusted_snapshot_state};
20+
std::string registryId;
21+
std::string message;
22+
};
23+
struct TrustedStateLoadResult
24+
{
25+
std::optional<TrustedRegistrySnapshotState> state;
26+
std::vector<TrustedStateStoreIssue> issues;
27+
[[nodiscard]] bool valid() const noexcept { return issues.empty(); }
28+
};
29+
struct TrustedStateCommitResult
30+
{
31+
bool committed{false};
32+
std::vector<TrustedStateStoreIssue> issues;
33+
[[nodiscard]] bool valid() const noexcept { return committed && issues.empty(); }
34+
};
35+
36+
// Security state is keyed only by logical Registry ID. Implementations must
37+
// compare-and-advance atomically; a blind overwrite API is intentionally absent.
38+
class TrustedRegistryStateStore
39+
{
40+
public:
41+
virtual ~TrustedRegistryStateStore() = default;
42+
[[nodiscard]] virtual TrustedStateLoadResult load(std::string_view registryId) const = 0;
43+
[[nodiscard]] virtual TrustedStateCommitResult compare_and_advance(
44+
const std::optional<TrustedRegistrySnapshotState> &expected,
45+
const TrustedRegistrySnapshotState &next) = 0;
46+
};
47+
48+
class InMemoryTrustedRegistryStateStore final : public TrustedRegistryStateStore
49+
{
50+
public:
51+
[[nodiscard]] TrustedStateLoadResult load(std::string_view registryId) const override;
52+
[[nodiscard]] TrustedStateCommitResult compare_and_advance(
53+
const std::optional<TrustedRegistrySnapshotState> &expected,
54+
const TrustedRegistrySnapshotState &next) override;
55+
private:
56+
std::vector<TrustedRegistrySnapshotState> states_;
57+
};
58+
59+
enum class MetadataCacheErrorCode
60+
{
61+
cache_conflict,
62+
invalid_registry_cache_key,
63+
invalid_git_cache_key,
64+
offline_trusted_state_unavailable,
65+
offline_registry_metadata_unavailable,
66+
offline_git_metadata_unavailable,
67+
offline_cached_metadata_trust_failure,
68+
online_registry_observation_rejected,
69+
registry_snapshot_changed_during_acquisition
70+
};
71+
struct MetadataCacheIssue
72+
{
73+
MetadataCacheErrorCode code{MetadataCacheErrorCode::cache_conflict};
74+
std::string subject;
75+
std::string message;
76+
};
77+
template <typename T>
78+
struct MetadataStorageResult
79+
{
80+
std::optional<T> value;
81+
std::vector<MetadataCacheIssue> issues;
82+
[[nodiscard]] bool valid() const noexcept { return value.has_value() && issues.empty(); }
83+
};
84+
85+
struct RegistryMetadataCacheEntry
86+
{
87+
UnverifiedRegistryObservation observation;
88+
};
89+
struct GitMetadataCacheEntry
90+
{
91+
GitPackageIdentity identity;
92+
GitRevisionRequirement revision;
93+
GitResolution resolution;
94+
};
95+
96+
class PackageMetadataCache;
97+
struct OnlineRegistryAdmission;
98+
99+
class RegistryObservationCacheAdmission final
100+
{
101+
public:
102+
[[nodiscard]] const UnverifiedRegistryObservation &observation() const noexcept { return observation_; }
103+
[[nodiscard]] const TrustedRegistrySnapshotState &proposed_state() const noexcept { return proposedState_; }
104+
private:
105+
friend struct OnlineRegistryAdmission;
106+
friend OnlineRegistryAdmission admit_online_registry_observation(
107+
const RegistryAuthorityDirectory &, const RegistryAuthorityVerifier &,
108+
const std::optional<TrustedRegistrySnapshotState> &, const RegistryPackageIdentity &,
109+
const UnverifiedRegistryObservation &, PackageMetadataCache &);
110+
RegistryObservationCacheAdmission(UnverifiedRegistryObservation observation,
111+
TrustedRegistrySnapshotState proposedState)
112+
: observation_(std::move(observation)), proposedState_(std::move(proposedState)) {}
113+
UnverifiedRegistryObservation observation_;
114+
TrustedRegistrySnapshotState proposedState_;
115+
};
116+
117+
// Cache entries are observations, not trust. Registry evidence is retained
118+
// so every read can cross P10J again. Cache lifetime is independent of the
119+
// trusted-state store and the later package-content store.
120+
class PackageMetadataCache
121+
{
122+
public:
123+
virtual ~PackageMetadataCache() = default;
124+
[[nodiscard]] virtual MetadataStorageResult<UnverifiedRegistryObservation> read_registry(
125+
const RegistrySnapshotIdentity &snapshot, const RegistryPackageIdentity &query) const = 0;
126+
[[nodiscard]] virtual MetadataStorageResult<bool> write_registry(
127+
const RegistryObservationCacheAdmission &admission) = 0;
128+
[[nodiscard]] virtual MetadataStorageResult<GitResolution> read_git(
129+
const GitPackageIdentity &identity, const GitRevisionRequirement &revision) const = 0;
130+
[[nodiscard]] virtual MetadataStorageResult<bool> write_git(
131+
const GitPackageIdentity &identity, const GitRevisionRequirement &revision,
132+
const GitResolution &resolution) = 0;
133+
};
134+
135+
class InMemoryPackageMetadataCache final : public PackageMetadataCache
136+
{
137+
public:
138+
[[nodiscard]] MetadataStorageResult<UnverifiedRegistryObservation> read_registry(
139+
const RegistrySnapshotIdentity &snapshot, const RegistryPackageIdentity &query) const override;
140+
[[nodiscard]] MetadataStorageResult<bool> write_registry(
141+
const RegistryObservationCacheAdmission &admission) override;
142+
[[nodiscard]] MetadataStorageResult<GitResolution> read_git(
143+
const GitPackageIdentity &identity, const GitRevisionRequirement &revision) const override;
144+
[[nodiscard]] MetadataStorageResult<bool> write_git(const GitPackageIdentity &identity,
145+
const GitRevisionRequirement &revision, const GitResolution &resolution) override;
146+
void clear() noexcept;
147+
private:
148+
std::vector<RegistryMetadataCacheEntry> registryEntries_;
149+
std::vector<GitMetadataCacheEntry> gitEntries_;
150+
};
151+
152+
enum class MetadataAccessMode { online, offline };
153+
154+
// Pure online admission gate: unverified source data is cached only after
155+
// P10J verification and P10K acceptance. The caller separately commits the
156+
// returned proposed state through compare_and_advance.
157+
struct OnlineRegistryAdmission
158+
{
159+
std::optional<TrustedRegistrySnapshotState> proposedState;
160+
std::optional<RegistryObservationCacheAdmission> cacheAdmission;
161+
std::vector<MetadataCacheIssue> issues;
162+
[[nodiscard]] bool valid() const noexcept { return proposedState.has_value() && issues.empty(); }
163+
};
164+
[[nodiscard]] OnlineRegistryAdmission admit_online_registry_observation(
165+
const RegistryAuthorityDirectory &authorities, const RegistryAuthorityVerifier &verifier,
166+
const std::optional<TrustedRegistrySnapshotState> &previous,
167+
const RegistryPackageIdentity &query, const UnverifiedRegistryObservation &observation,
168+
PackageMetadataCache &cache);
169+
170+
// Offline adapter never calls an external source. Registry cache lookups use
171+
// exactly the persisted trusted snapshot and are re-verified and pinned.
172+
class OfflineCachedPackageMetadataSource final : public PackageMetadataSource
173+
{
174+
public:
175+
OfflineCachedPackageMetadataSource(const TrustedRegistryStateStore &trustedStates,
176+
const PackageMetadataCache &cache, const RegistryAuthorityDirectory &authorities,
177+
const RegistryAuthorityVerifier &verifier);
178+
[[nodiscard]] RegistryQueryResult registry_releases(const RegistryPackageIdentity &identity) override;
179+
[[nodiscard]] GitQueryResult resolve_git(const GitPackageIdentity &identity,
180+
const GitRevisionRequirement &revision) override;
181+
[[nodiscard]] const std::vector<RegistrySnapshotPin> &pins() const noexcept { return pins_; }
182+
[[nodiscard]] const std::vector<MetadataCacheIssue> &last_issues() const noexcept { return lastIssues_; }
183+
private:
184+
const TrustedRegistryStateStore &trustedStates_;
185+
const PackageMetadataCache &cache_;
186+
const RegistryAuthorityDirectory &authorities_;
187+
const RegistryAuthorityVerifier &verifier_;
188+
std::vector<RegistrySnapshotPin> pins_;
189+
std::vector<MetadataCacheIssue> lastIssues_;
190+
};
191+
} // namespace vix::cli::project::native
192+
193+
#endif // VIX_CLI_PROJECT_NATIVE_METADATA_STORAGE_HPP

0 commit comments

Comments
 (0)