feat: trust/exposure fields — installVerification, keyPinned, exposure
Some checks failed
Security / Security check (push) Failing after 2s

Regenerated stubs for the additive proto: per-entry
StoreEntry.installVerification, StoreSource.keyPinned, the
ListStoresResponse policy snapshot (requireSignatures /
trustedPublisherCount) and DeclaredService.exposure. New
listStoresFull() exposes the response-level snapshot; listStores()
stays as the plain source list.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-07-21 12:56:35 +02:00
parent d9ef3ecdb6
commit 0eafaa763a
4 changed files with 143 additions and 4 deletions

View file

@ -6,6 +6,14 @@ for the authoritative per-release detail.
## Unreleased ## Unreleased
- Regenerated stubs for the trust/exposure proto additions:
`StoreEntry.installVerification` (pinned-key / trusted-publishers /
unverified / blocked / federated), `StoreSource.keyPinned`,
`ListStoresResponse.requireSignatures` / `trustedPublisherCount`,
`DeclaredService.exposure` (loopback/private/public/unknown).
- New `listStoresFull()` returning the full response incl. the
policy snapshot; `listStores()` unchanged.
- `InvocationList.detachedEnabled`: whether the operator enabled - `InvocationList.detachedEnabled`: whether the operator enabled
detached invocations — monitor UIs can distinguish "feature off" detached invocations — monitor UIs can distinguish "feature off"
from "on, but no runs yet". Regenerated stubs. from "on, but no runs yet". Regenerated stubs.

View file

@ -4962,12 +4962,14 @@ class DeclaredService extends $pb.GeneratedMessage {
$core.String? endpoint, $core.String? endpoint,
$core.String? healthPath, $core.String? healthPath,
$core.Iterable<$core.String>? tags, $core.Iterable<$core.String>? tags,
$core.String? exposure,
}) { }) {
final result = create(); final result = create();
if (name != null) result.name = name; if (name != null) result.name = name;
if (endpoint != null) result.endpoint = endpoint; if (endpoint != null) result.endpoint = endpoint;
if (healthPath != null) result.healthPath = healthPath; if (healthPath != null) result.healthPath = healthPath;
if (tags != null) result.tags.addAll(tags); if (tags != null) result.tags.addAll(tags);
if (exposure != null) result.exposure = exposure;
return result; return result;
} }
@ -4988,6 +4990,7 @@ class DeclaredService extends $pb.GeneratedMessage {
..aOS(2, _omitFieldNames ? '' : 'endpoint') ..aOS(2, _omitFieldNames ? '' : 'endpoint')
..aOS(3, _omitFieldNames ? '' : 'healthPath') ..aOS(3, _omitFieldNames ? '' : 'healthPath')
..pPS(4, _omitFieldNames ? '' : 'tags') ..pPS(4, _omitFieldNames ? '' : 'tags')
..aOS(5, _omitFieldNames ? '' : 'exposure')
..hasRequiredFields = false; ..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -5039,6 +5042,23 @@ class DeclaredService extends $pb.GeneratedMessage {
@$pb.TagNumber(4) @$pb.TagNumber(4)
$pb.PbList<$core.String> get tags => $_getList(3); $pb.PbList<$core.String> get tags => $_getList(3);
/// Network reach of `endpoint`, classified from its host part
/// (no DNS lookups named hosts other than localhost report
/// "unknown"):
/// "loopback" 127.0.0.0/8, ::1, localhost
/// "private" RFC1918 / ULA / link-local ranges
/// "public" any other literal IP
/// "unknown" unparseable, or a hostname we cannot judge
/// Empty from pre-0.23 hubs.
@$pb.TagNumber(5)
$core.String get exposure => $_getSZ(4);
@$pb.TagNumber(5)
set exposure($core.String value) => $_setString(4, value);
@$pb.TagNumber(5)
$core.bool hasExposure() => $_has(4);
@$pb.TagNumber(5)
void clearExposure() => $_clearField(5);
} }
class StoreEntry extends $pb.GeneratedMessage { class StoreEntry extends $pb.GeneratedMessage {
@ -5067,6 +5087,7 @@ class StoreEntry extends $pb.GeneratedMessage {
$core.String? source, $core.String? source,
$core.String? canonicalCategory, $core.String? canonicalCategory,
$core.String? canonicalCategoryLabel, $core.String? canonicalCategoryLabel,
$core.String? installVerification,
}) { }) {
final result = create(); final result = create();
if (name != null) result.name = name; if (name != null) result.name = name;
@ -5096,6 +5117,8 @@ class StoreEntry extends $pb.GeneratedMessage {
if (canonicalCategory != null) result.canonicalCategory = canonicalCategory; if (canonicalCategory != null) result.canonicalCategory = canonicalCategory;
if (canonicalCategoryLabel != null) if (canonicalCategoryLabel != null)
result.canonicalCategoryLabel = canonicalCategoryLabel; result.canonicalCategoryLabel = canonicalCategoryLabel;
if (installVerification != null)
result.installVerification = installVerification;
return result; return result;
} }
@ -5136,6 +5159,7 @@ class StoreEntry extends $pb.GeneratedMessage {
..aOS(22, _omitFieldNames ? '' : 'source') ..aOS(22, _omitFieldNames ? '' : 'source')
..aOS(23, _omitFieldNames ? '' : 'canonicalCategory') ..aOS(23, _omitFieldNames ? '' : 'canonicalCategory')
..aOS(24, _omitFieldNames ? '' : 'canonicalCategoryLabel') ..aOS(24, _omitFieldNames ? '' : 'canonicalCategoryLabel')
..aOS(25, _omitFieldNames ? '' : 'installVerification')
..hasRequiredFields = false; ..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -5396,6 +5420,33 @@ class StoreEntry extends $pb.GeneratedMessage {
$core.bool hasCanonicalCategoryLabel() => $_has(23); $core.bool hasCanonicalCategoryLabel() => $_has(23);
@$pb.TagNumber(24) @$pb.TagNumber(24)
void clearCanonicalCategoryLabel() => $_clearField(24); void clearCanonicalCategoryLabel() => $_clearField(24);
/// How an install of this entry would be verified, given its
/// origin store and the operator's CURRENT security policy. The
/// hub computes this with the same resolution the install path
/// enforces (pinned store key -> trusted publishers -> policy),
/// so the display can never disagree with the gate:
/// "pinned-key" verified against the origin store's
/// pinned cosign key (or the built-in
/// official key for bundled/official)
/// "trusted-publishers" verified against the applicable
/// trusted-publisher list
/// "unverified" require_signatures is off; installs
/// are NOT signature-checked
/// "blocked" policy requires signatures but no key
/// material applies to this source; an
/// install would be refused
/// "federated" bridge entry (MCP/n8n/); no bundle
/// signature applies
/// Empty from pre-0.23 hubs clients keep their generic note.
@$pb.TagNumber(25)
$core.String get installVerification => $_getSZ(24);
@$pb.TagNumber(25)
set installVerification($core.String value) => $_setString(24, value);
@$pb.TagNumber(25)
$core.bool hasInstallVerification() => $_has(24);
@$pb.TagNumber(25)
void clearInstallVerification() => $_clearField(25);
} }
class ChannelStatusResponse extends $pb.GeneratedMessage { class ChannelStatusResponse extends $pb.GeneratedMessage {
@ -8963,12 +9014,14 @@ class StoreSource extends $pb.GeneratedMessage {
$core.String? url, $core.String? url,
$core.bool? removable, $core.bool? removable,
$core.int? moduleCount, $core.int? moduleCount,
$core.bool? keyPinned,
}) { }) {
final result = create(); final result = create();
if (name != null) result.name = name; if (name != null) result.name = name;
if (url != null) result.url = url; if (url != null) result.url = url;
if (removable != null) result.removable = removable; if (removable != null) result.removable = removable;
if (moduleCount != null) result.moduleCount = moduleCount; if (moduleCount != null) result.moduleCount = moduleCount;
if (keyPinned != null) result.keyPinned = keyPinned;
return result; return result;
} }
@ -8989,6 +9042,7 @@ class StoreSource extends $pb.GeneratedMessage {
..aOS(2, _omitFieldNames ? '' : 'url') ..aOS(2, _omitFieldNames ? '' : 'url')
..aOB(3, _omitFieldNames ? '' : 'removable') ..aOB(3, _omitFieldNames ? '' : 'removable')
..aI(4, _omitFieldNames ? '' : 'moduleCount') ..aI(4, _omitFieldNames ? '' : 'moduleCount')
..aOB(5, _omitFieldNames ? '' : 'keyPinned')
..hasRequiredFields = false; ..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -9049,14 +9103,32 @@ class StoreSource extends $pb.GeneratedMessage {
$core.bool hasModuleCount() => $_has(3); $core.bool hasModuleCount() => $_has(3);
@$pb.TagNumber(4) @$pb.TagNumber(4)
void clearModuleCount() => $_clearField(4); void clearModuleCount() => $_clearField(4);
/// True when this store pins a cosign publisher key
/// (`trust.public_key_pem`, or the built-in official key for the
/// bundled seed) installs from it are verified against that key
/// whenever signature checking is on.
@$pb.TagNumber(5)
$core.bool get keyPinned => $_getBF(4);
@$pb.TagNumber(5)
set keyPinned($core.bool value) => $_setBool(4, value);
@$pb.TagNumber(5)
$core.bool hasKeyPinned() => $_has(4);
@$pb.TagNumber(5)
void clearKeyPinned() => $_clearField(5);
} }
class ListStoresResponse extends $pb.GeneratedMessage { class ListStoresResponse extends $pb.GeneratedMessage {
factory ListStoresResponse({ factory ListStoresResponse({
$core.Iterable<StoreSource>? stores, $core.Iterable<StoreSource>? stores,
$core.bool? requireSignatures,
$core.int? trustedPublisherCount,
}) { }) {
final result = create(); final result = create();
if (stores != null) result.stores.addAll(stores); if (stores != null) result.stores.addAll(stores);
if (requireSignatures != null) result.requireSignatures = requireSignatures;
if (trustedPublisherCount != null)
result.trustedPublisherCount = trustedPublisherCount;
return result; return result;
} }
@ -9075,6 +9147,8 @@ class ListStoresResponse extends $pb.GeneratedMessage {
createEmptyInstance: create) createEmptyInstance: create)
..pPM<StoreSource>(1, _omitFieldNames ? '' : 'stores', ..pPM<StoreSource>(1, _omitFieldNames ? '' : 'stores',
subBuilder: StoreSource.create) subBuilder: StoreSource.create)
..aOB(2, _omitFieldNames ? '' : 'requireSignatures')
..aI(3, _omitFieldNames ? '' : 'trustedPublisherCount')
..hasRequiredFields = false; ..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -9098,6 +9172,28 @@ class ListStoresResponse extends $pb.GeneratedMessage {
@$pb.TagNumber(1) @$pb.TagNumber(1)
$pb.PbList<StoreSource> get stores => $_getList(0); $pb.PbList<StoreSource> get stores => $_getList(0);
/// Operator policy snapshot so store UIs can say honestly whether
/// installs are signature-checked at all.
@$pb.TagNumber(2)
$core.bool get requireSignatures => $_getBF(1);
@$pb.TagNumber(2)
set requireSignatures($core.bool value) => $_setBool(1, value);
@$pb.TagNumber(2)
$core.bool hasRequireSignatures() => $_has(1);
@$pb.TagNumber(2)
void clearRequireSignatures() => $_clearField(2);
/// Number of globally trusted publisher identities
/// (`security.trusted_publishers`).
@$pb.TagNumber(3)
$core.int get trustedPublisherCount => $_getIZ(2);
@$pb.TagNumber(3)
set trustedPublisherCount($core.int value) => $_setSignedInt32(2, value);
@$pb.TagNumber(3)
$core.bool hasTrustedPublisherCount() => $_has(2);
@$pb.TagNumber(3)
void clearTrustedPublisherCount() => $_clearField(3);
} }
class AddStoreRequest extends $pb.GeneratedMessage { class AddStoreRequest extends $pb.GeneratedMessage {

View file

@ -1311,6 +1311,7 @@ const DeclaredService$json = {
{'1': 'endpoint', '3': 2, '4': 1, '5': 9, '10': 'endpoint'}, {'1': 'endpoint', '3': 2, '4': 1, '5': 9, '10': 'endpoint'},
{'1': 'health_path', '3': 3, '4': 1, '5': 9, '10': 'healthPath'}, {'1': 'health_path', '3': 3, '4': 1, '5': 9, '10': 'healthPath'},
{'1': 'tags', '3': 4, '4': 3, '5': 9, '10': 'tags'}, {'1': 'tags', '3': 4, '4': 3, '5': 9, '10': 'tags'},
{'1': 'exposure', '3': 5, '4': 1, '5': 9, '10': 'exposure'},
], ],
}; };
@ -1318,7 +1319,7 @@ const DeclaredService$json = {
final $typed_data.Uint8List declaredServiceDescriptor = $convert.base64Decode( final $typed_data.Uint8List declaredServiceDescriptor = $convert.base64Decode(
'Cg9EZWNsYXJlZFNlcnZpY2USEgoEbmFtZRgBIAEoCVIEbmFtZRIaCghlbmRwb2ludBgCIAEoCV' 'Cg9EZWNsYXJlZFNlcnZpY2USEgoEbmFtZRgBIAEoCVIEbmFtZRIaCghlbmRwb2ludBgCIAEoCV'
'IIZW5kcG9pbnQSHwoLaGVhbHRoX3BhdGgYAyABKAlSCmhlYWx0aFBhdGgSEgoEdGFncxgEIAMo' 'IIZW5kcG9pbnQSHwoLaGVhbHRoX3BhdGgYAyABKAlSCmhlYWx0aFBhdGgSEgoEdGFncxgEIAMo'
'CVIEdGFncw=='); 'CVIEdGFncxIaCghleHBvc3VyZRgFIAEoCVIIZXhwb3N1cmU=');
@$core.Deprecated('Use storeEntryDescriptor instead') @$core.Deprecated('Use storeEntryDescriptor instead')
const StoreEntry$json = { const StoreEntry$json = {
@ -1372,6 +1373,13 @@ const StoreEntry$json = {
'5': 9, '5': 9,
'10': 'canonicalCategoryLabel' '10': 'canonicalCategoryLabel'
}, },
{
'1': 'install_verification',
'3': 25,
'4': 1,
'5': 9,
'10': 'installVerification'
},
], ],
}; };
@ -1391,7 +1399,8 @@ final $typed_data.Uint8List storeEntryDescriptor = $convert.base64Decode(
'oIcHJvdmlkZXIYFCABKAlSCHByb3ZpZGVyEh8KC3NvdXJjZV9raW5kGBUgASgJUgpzb3VyY2VL' 'oIcHJvdmlkZXIYFCABKAlSCHByb3ZpZGVyEh8KC3NvdXJjZV9raW5kGBUgASgJUgpzb3VyY2VL'
'aW5kEhYKBnNvdXJjZRgWIAEoCVIGc291cmNlEi0KEmNhbm9uaWNhbF9jYXRlZ29yeRgXIAEoCV' 'aW5kEhYKBnNvdXJjZRgWIAEoCVIGc291cmNlEi0KEmNhbm9uaWNhbF9jYXRlZ29yeRgXIAEoCV'
'IRY2Fub25pY2FsQ2F0ZWdvcnkSOAoYY2Fub25pY2FsX2NhdGVnb3J5X2xhYmVsGBggASgJUhZj' 'IRY2Fub25pY2FsQ2F0ZWdvcnkSOAoYY2Fub25pY2FsX2NhdGVnb3J5X2xhYmVsGBggASgJUhZj'
'YW5vbmljYWxDYXRlZ29yeUxhYmVs'); 'YW5vbmljYWxDYXRlZ29yeUxhYmVsEjEKFGluc3RhbGxfdmVyaWZpY2F0aW9uGBkgASgJUhNpbn'
'N0YWxsVmVyaWZpY2F0aW9u');
@$core.Deprecated('Use channelStatusResponseDescriptor instead') @$core.Deprecated('Use channelStatusResponseDescriptor instead')
const ChannelStatusResponse$json = { const ChannelStatusResponse$json = {
@ -2179,6 +2188,7 @@ const StoreSource$json = {
{'1': 'url', '3': 2, '4': 1, '5': 9, '10': 'url'}, {'1': 'url', '3': 2, '4': 1, '5': 9, '10': 'url'},
{'1': 'removable', '3': 3, '4': 1, '5': 8, '10': 'removable'}, {'1': 'removable', '3': 3, '4': 1, '5': 8, '10': 'removable'},
{'1': 'module_count', '3': 4, '4': 1, '5': 5, '10': 'moduleCount'}, {'1': 'module_count', '3': 4, '4': 1, '5': 5, '10': 'moduleCount'},
{'1': 'key_pinned', '3': 5, '4': 1, '5': 8, '10': 'keyPinned'},
], ],
}; };
@ -2186,7 +2196,7 @@ const StoreSource$json = {
final $typed_data.Uint8List storeSourceDescriptor = $convert.base64Decode( final $typed_data.Uint8List storeSourceDescriptor = $convert.base64Decode(
'CgtTdG9yZVNvdXJjZRISCgRuYW1lGAEgASgJUgRuYW1lEhAKA3VybBgCIAEoCVIDdXJsEhwKCX' 'CgtTdG9yZVNvdXJjZRISCgRuYW1lGAEgASgJUgRuYW1lEhAKA3VybBgCIAEoCVIDdXJsEhwKCX'
'JlbW92YWJsZRgDIAEoCFIJcmVtb3ZhYmxlEiEKDG1vZHVsZV9jb3VudBgEIAEoBVILbW9kdWxl' 'JlbW92YWJsZRgDIAEoCFIJcmVtb3ZhYmxlEiEKDG1vZHVsZV9jb3VudBgEIAEoBVILbW9kdWxl'
'Q291bnQ='); 'Q291bnQSHQoKa2V5X3Bpbm5lZBgFIAEoCFIJa2V5UGlubmVk');
@$core.Deprecated('Use listStoresResponseDescriptor instead') @$core.Deprecated('Use listStoresResponseDescriptor instead')
const ListStoresResponse$json = { const ListStoresResponse$json = {
@ -2200,13 +2210,29 @@ const ListStoresResponse$json = {
'6': '.chain.v1.StoreSource', '6': '.chain.v1.StoreSource',
'10': 'stores' '10': 'stores'
}, },
{
'1': 'require_signatures',
'3': 2,
'4': 1,
'5': 8,
'10': 'requireSignatures'
},
{
'1': 'trusted_publisher_count',
'3': 3,
'4': 1,
'5': 5,
'10': 'trustedPublisherCount'
},
], ],
}; };
/// Descriptor for `ListStoresResponse`. Decode as a `google.protobuf.DescriptorProto`. /// Descriptor for `ListStoresResponse`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List listStoresResponseDescriptor = $convert.base64Decode( final $typed_data.Uint8List listStoresResponseDescriptor = $convert.base64Decode(
'ChJMaXN0U3RvcmVzUmVzcG9uc2USLQoGc3RvcmVzGAEgAygLMhUuY2hhaW4udjEuU3RvcmVTb3' 'ChJMaXN0U3RvcmVzUmVzcG9uc2USLQoGc3RvcmVzGAEgAygLMhUuY2hhaW4udjEuU3RvcmVTb3'
'VyY2VSBnN0b3Jlcw=='); 'VyY2VSBnN0b3JlcxItChJyZXF1aXJlX3NpZ25hdHVyZXMYAiABKAhSEXJlcXVpcmVTaWduYXR1'
'cmVzEjYKF3RydXN0ZWRfcHVibGlzaGVyX2NvdW50GAMgASgFUhV0cnVzdGVkUHVibGlzaGVyQ2'
'91bnQ=');
@$core.Deprecated('Use addStoreRequestDescriptor instead') @$core.Deprecated('Use addStoreRequestDescriptor instead')
const AddStoreRequest$json = { const AddStoreRequest$json = {

View file

@ -75,6 +75,7 @@ typedef CheckUpdateResponse = pb.CheckUpdateResponse;
typedef ChannelStatusResponse = pb.ChannelStatusResponse; typedef ChannelStatusResponse = pb.ChannelStatusResponse;
typedef ChannelEntry = pb.ChannelEntry; typedef ChannelEntry = pb.ChannelEntry;
typedef StoreSource = pb.StoreSource; typedef StoreSource = pb.StoreSource;
typedef ListStoresResponse = pb.ListStoresResponse;
typedef AddStoreResponse = pb.AddStoreResponse; typedef AddStoreResponse = pb.AddStoreResponse;
typedef RemoveStoreResponse = pb.RemoveStoreResponse; typedef RemoveStoreResponse = pb.RemoveStoreResponse;
typedef SystemAiStatusResponse = pb.SystemAiStatusResponse; typedef SystemAiStatusResponse = pb.SystemAiStatusResponse;
@ -764,6 +765,14 @@ class HubClient {
return r.stores; return r.stores;
} }
/// Full store listing including the operator policy snapshot
/// (`requireSignatures`, `trustedPublisherCount`) the hub reports
/// alongside the sources since 0.23 store UIs use it to state
/// honestly whether installs are signature-checked at all.
/// [listStores] stays as the plain source list.
Future<ListStoresResponse> listStoresFull() =>
_admin.listStores(pb.ListStoresRequest());
/// Register a new module store. The hub fetches + validates its index, /// Register a new module store. The hub fetches + validates its index,
/// persists it to config, and merges it live so its modules are /// persists it to config, and merges it live so its modules are
/// immediately searchable + installable. /// immediately searchable + installable.