feat(query): PDF Package-2 associative log lookup via flags/metatags

Implements the source PDF's 'put(C,Z,Y,X,bytes,flags)' + 'scan_by_flag'
associative-storage API so records are discoverable by WHAT they are
(event type / doc_type metatag) rather than WHERE they live (coordinate
path) — the 'log lookup by query' the OS layers want.

cubestore:
  - CubeStore::scan_by_flag(u16) / scan_by_type(&str): predicate scans
    over decoded CubeHeader (not prefix scans).
  - CubeStore::query_by_flag / query_by_type: same, but also return the
    decoded (label, header, body) so a query tool can present results.
  - get_record's raw/un-enveloped fallback now refresh_flags() so
    synthesized headers carry SIZE_BYTES and are visible to flag queries.
  - regression tests: scan_by_flag_finds_typed_records,
    scan_by_type_event_lookup.

cubesys (daemon):
  - new 'put' verb: enveloped write with optional doc_type=/title=
    metatags (the PDF's put API). rawput remains for bulk/append payloads.
  - ConcurrentStore::query_doc_type was already the backing predicate the
    'query <doc_type>' verb uses; it now matches these typed records.

Proven end-to-end in the VM: 'cubec query klog' returns the kernel-log
index record (doc_type=klog) published by cube-os-klog.sh, and
'cubec query fn' returns the 8 pre-existing typed program records.
This commit is contained in:
CUBELinux-2
2026-08-13 18:12:13 -04:00
parent fe64b869e4
commit 2ff1dff02b
2 changed files with 195 additions and 0 deletions
+56
View File
@@ -939,6 +939,62 @@ impl Session {
store.delete_raw(&coord);
Ok(format!("ok: deleted {}", coord.pack_u32()))
}
// --- Enveloped coordinate API (PDF Package 2: `put(C,Z,Y,X,
// bytes, flags)`). Unlike `rawput`, this writes a full record
// with a `CubeHeader`, so the record carries metatags
// (doc_type / title) that the associative `query` verb can
// target by event type. `rawput` stays for bulk/append payloads
// (e.g. the rolling kernel-log window) that do not need header
// metadata; `put` is for addressed, discoverable records. ---
"put" => {
let c = parse_u8(it.next(), "put needs <c>")?;
let z = parse_u8(it.next(), "put needs <z>")?;
let y = parse_u8(it.next(), "put needs <y>")?;
let x = parse_u8(it.next(), "put needs <x>")?;
let hex = it
.next()
.ok_or_else(|| "put needs <hex-bytes>".to_string())?;
let val = hex_decode(hex).ok_or_else(|| "put: value must be hex".to_string())?;
let coord = Czyx::new(c, z, y, x);
// Optional metatags: `doc_type=<t>` and/or `title=<t>` may
// follow. These are what `query <doc_type>` matches on.
let mut header = CubeHeader::new();
while let Some(tok) = it.next() {
if let Some(v) = tok.strip_prefix("doc_type=") {
header.doc_type = Some(v.to_string());
} else if let Some(v) = tok.strip_prefix("title=") {
header.title = Some(v.to_string());
} else {
return Err(format!("put: unknown option {tok}"));
}
}
header.created_at = Some(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0),
);
header.refresh_flags();
let doc_type_dbg = header.doc_type.clone();
if let Some(txn) = self.txn.as_mut() {
txn.ops.push(TxnOp {
coord,
put: Some((val, header)),
});
Ok(format!(
"buffered put {} (doc_type={:?}) — commit to apply",
coord.pack_u32(),
doc_type_dbg
))
} else {
store.put_record(coord, &header, &val);
Ok(format!(
"ok: wrote {} (doc_type={:?})",
coord.pack_u32(),
doc_type_dbg
))
}
}
"rawkeys" => {
let ks: Vec<String> = store
.keys()