Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .github/workflows/actions.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# This file is machine-generated by `gh actions-lock`.
# Do not edit by hand; run `gh actions-lock` to update.
# Docs: https://gh.io/actions-lockfile
version: 'v0.0.2'
workflows:
'.github/workflows/casket-pages.yml':
- 'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9'
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d'
- 'actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346'
- 'actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9'
- 'haskell-actions/setup@0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d'
'.github/workflows/codeql.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'github/codeql-action@1c5b675653bb5c22dbe9b12b556ec555138e09fd'
'.github/workflows/dogfood-gate.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'hyperpolymath/deed-ecosystem@aa4b836bd969df2bc58128cb8e3d20bbc88d5e79'
- 'hyperpolymath/k9-ecosystem@89f3c2702f4f650a92aa7411502f38da06abd562'
'.github/workflows/hpc-ci.yml':
- 'actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830'
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
- 'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c'
- 'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a'
- 'mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29'
'.github/workflows/instant-sync.yml':
- 'peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697'
'.github/workflows/push-email-notify.yml':
- 'hyperpolymath/smtp-notify-action@22e7bdb322c430c1d0dac6b3bb307f4bb139d0be'
'.github/workflows/workflow-linter.yml':
- 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1'
dependencies:
'actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830':
ref: 'v4.3.0'
commit: 'sha1-0057852bfaa89a56745cba8c7296529d2fc39830'
owner_id: 44036562
repo_id: 215566462
'actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9':
ref: 'v6.1.0'
commit: 'sha1-55cc8345863c7cc4c66a329aec7e433d2d1c52a9'
owner_id: 44036562
repo_id: 215566462
'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1':
ref: 'v7.0.1'
commit: 'sha1-3d3c42e5aac5ba805825da76410c181273ba90b1'
owner_id: 44036562
repo_id: 197814629
'actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d':
ref: 'v6.0.0'
commit: 'sha1-45bfe0192ca1faeb007ade9deae92b16b8254a0d'
owner_id: 44036562
repo_id: 513659658
'actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346':
ref: 'v5.0.1'
commit: 'sha1-368f82528645a54fb793d4d04e342629a3f51346'
owner_id: 44036562
repo_id: 438112499
'actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c':
ref: 'v8.0.1'
commit: 'sha1-3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c'
owner_id: 44036562
repo_id: 192626254
'actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a':
ref: 'v7.0.1'
commit: 'sha1-043fb46d1a93c77aae656e7c1c64a875d1fc6a0a'
owner_id: 44036562
repo_id: 192625955
'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f':
ref: 'v7.0.0'
commit: 'sha1-bbbca2ddaa5d8feaa63e36b76fdaad77386f024f'
owner_id: 44036562
repo_id: 192625955
'actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9':
ref: 'v5.0.0'
commit: 'sha1-fc324d3547104276b827a68afc52ff2a11cc49c9'
owner_id: 44036562
repo_id: 496012378
uses:
- 'actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f'
'github/codeql-action@1c5b675653bb5c22dbe9b12b556ec555138e09fd':
ref: 'v4.38.1'
commit: 'sha1-1c5b675653bb5c22dbe9b12b556ec555138e09fd'
owner_id: 9919
repo_id: 259445878
'haskell-actions/setup@0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d':
ref: 'v2.12.1'
commit: 'sha1-0f8e8c99d88aeb3fbfd523f1ef2c6f762d10d64d'
owner_id: 75048950
repo_id: 623796603
'hyperpolymath/deed-ecosystem@aa4b836bd969df2bc58128cb8e3d20bbc88d5e79':
ref: 'main'
commit: 'sha1-aa4b836bd969df2bc58128cb8e3d20bbc88d5e79'
owner_id: 6759885
repo_id: 1275649586
'hyperpolymath/k9-ecosystem@89f3c2702f4f650a92aa7411502f38da06abd562':
ref: 'main'
commit: 'sha1-89f3c2702f4f650a92aa7411502f38da06abd562'
owner_id: 6759885
repo_id: 1275650185
'hyperpolymath/smtp-notify-action@22e7bdb322c430c1d0dac6b3bb307f4bb139d0be':
ref: 'v0.3.0'
commit: 'sha1-22e7bdb322c430c1d0dac6b3bb307f4bb139d0be'
owner_id: 6759885
repo_id: 1352485172
'mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29':
ref: 'v2.2.1'
commit: 'sha1-d1434d08867e3ee9daa34448df10607b98908d29'
owner_id: 7289241
repo_id: 812112570
'peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697':
ref: 'v4.0.1'
commit: 'sha1-28959ce8df70de7be546dd1250a005dd32156697'
owner_id: 18365890
repo_id: 220359305
1 change: 1 addition & 0 deletions .github/workflows/boj-build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: BoJ Server Build Trigger
# Opt-in: GitHub-hosted runners cannot resolve the old boj-server.local URL.
on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/casket-pages.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: GitHub Pages

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: CodeQL Security Analysis

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/dogfood-gate.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
#
# dogfood-gate.yml — Hyperpolymath Dogfooding Quality Gate
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/governance.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Governance

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/hpc-ci.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# Docudactyl HPC — Chapel + Zig FFI CI Pipeline
#
# Three jobs:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/hypatia-scan.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Hypatia Security Scan

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/instant-sync.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# Instant Forge Sync - Triggers propagation to all forges on push/release
name: Instant Sync

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/label-triage.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Label Triage

# Classify newly-filed issues against the estate label taxonomy.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/labels.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Labels

# Applies the canonical estate label set from .github/labels.json.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/mirror.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Mirror to Git Forges

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/push-email-notify.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# Dormant push-email notification. ARMED by setting the repo variable
# PUSH_EMAIL_ENABLED=true (the single on/off switch). Addresses are pre-filled;
# sending needs the org SMTP secrets (SMTP_HOST/PORT/USER/PASS). Inherited by
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: OSSF Scorecard

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/secret-scanner.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
name: Secret Scanner

on:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/workflow-linter.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: MPL-2.0
# This workflow is managed by gh actions-lock.
# Prevention workflow - validates all workflows have proper security config
name: Workflow Security Linter

Expand Down
6 changes: 3 additions & 3 deletions ffi/zig/src/gpu_ocr.zig
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ pub export fn ddac_gpu_ocr_submit(

/// Flush any pending images in the queue — process them as a (partial) batch.
/// Call this before collecting results if the queue isn't full.
export fn ddac_gpu_ocr_flush(handle: ?*anyopaque) void {
pub export fn ddac_gpu_ocr_flush(handle: ?*anyopaque) void {
const ptr = handle orelse return;
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
Expand All @@ -395,7 +395,7 @@ export fn ddac_gpu_ocr_flush(handle: ?*anyopaque) void {
}

/// Get the number of results ready to collect after flush.
export fn ddac_gpu_ocr_results_ready(handle: ?*anyopaque) u32 {
pub export fn ddac_gpu_ocr_results_ready(handle: ?*anyopaque) u32 {
const ptr = handle orelse return 0;
// SAFETY: ptr originates from ddac_gpu_ocr_init() which stores a *GpuOcrState via @ptrCast; alignment is guaranteed by c_allocator
const state: *GpuOcrState = @ptrCast(@alignCast(ptr));
Expand All @@ -405,7 +405,7 @@ export fn ddac_gpu_ocr_results_ready(handle: ?*anyopaque) u32 {
/// Collect one OCR result by slot ID.
/// result_out must point to an OcrResult (48 bytes).
/// Returns 0 on success, -1 on invalid slot.
export fn ddac_gpu_ocr_collect(
pub export fn ddac_gpu_ocr_collect(
handle: ?*anyopaque,
slot_id: u32,
result_out: *OcrResult,
Expand Down
2 changes: 1 addition & 1 deletion ffi/zig/src/ml_inference.zig
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ export fn ddac_ml_set_model_dir(handle: ?*anyopaque, dir: [*:0]const u8) void {
/// input_path: path to the document/image/audio file
/// result_out: pointer to MlResult (56 bytes)
/// Returns 0 on success (check result.status for stage-level status).
export fn ddac_ml_run_stage(
pub export fn ddac_ml_run_stage(
handle: ?*anyopaque,
stage: u8,
input_path: [*:0]const u8,
Expand Down
66 changes: 54 additions & 12 deletions ffi/zig/src/stages.zig
Original file line number Diff line number Diff line change
Expand Up @@ -528,21 +528,19 @@ fn stageTocExtract(b: *capnp.Builder, input_path: [*:0]const u8) void {
}
defer c.g_object_unref(doc);

const iter_ptr = c.poppler_index_iter_new(doc);
if (iter_ptr == null) return;
defer c.poppler_index_iter_free(iter_ptr);

// First pass: count entries (max 100)
var entry_count: usize = 0;
const max_entries: usize = 100;
countTocEntries(iter_ptr, &entry_count, max_entries, 0);
{
const iter_ptr = c.poppler_index_iter_new(doc) orelse return;
defer c.poppler_index_iter_free(iter_ptr);
countTocEntries(iter_ptr, &entry_count, max_entries, 0);
}

if (entry_count == 0) return;

// Re-create iterator for second pass
c.poppler_index_iter_free(iter_ptr);
const iter2 = c.poppler_index_iter_new(doc);
if (iter2 == null) return;
const iter2 = c.poppler_index_iter_new(doc) orelse return;
defer c.poppler_index_iter_free(iter2);

// Allocate composite list
Expand All @@ -563,8 +561,7 @@ fn countTocEntries(iter_ptr: *c.PopplerIndexIter, count: *usize, max: usize, dep
defer c.poppler_action_free(action);
if (action.*.any.title != null) count.* += 1;
}
const child = c.poppler_index_iter_get_child(iter_ptr);
if (child != null) {
if (c.poppler_index_iter_get_child(iter_ptr)) |child| {
countTocEntries(child, count, max, depth + 1);
c.poppler_index_iter_free(child);
}
Expand All @@ -586,8 +583,7 @@ fn writeTocEntries(list: *capnp.CompositeList, iter_ptr: *c.PopplerIndexIter, id
idx.* += 1;
}
}
const child = c.poppler_index_iter_get_child(iter_ptr);
if (child != null) {
if (c.poppler_index_iter_get_child(iter_ptr)) |child| {
writeTocEntries(list, child, idx, max, depth + 1);
c.poppler_index_iter_free(child);
}
Expand Down Expand Up @@ -1327,3 +1323,49 @@ pub fn runStages(ctx: StageContext) void {
std.log.err("Failed to write stages Cap'n Proto output: {s}", .{@errorName(err)});
};
}

test "TOC extraction handles nested entries and absent outlines" {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
const path = try tmp.dir.realpathAlloc(std.testing.allocator, ".");
defer std.testing.allocator.free(path);
const pdf_path = try std.fmt.allocPrintSentinel(std.testing.allocator, "{s}/toc.pdf", .{path}, 0);
defer std.testing.allocator.free(pdf_path);

const fixture = @embedFile("testdata/toc.pdf");
var pdf: [fixture.len]u8 = fixture.*;
for ([_]bool{ true, false }) |has_outline| {
if (!has_outline) {
const ref = "/Outlines 4 0 R";
const offset = std.mem.indexOf(u8, &pdf, ref).?;
// Keep all xref offsets intact while removing the outline link.
@memset(pdf[offset .. offset + ref.len], ' ');
}
try tmp.dir.writeFile(.{ .sub_path = "toc.pdf", .data = &pdf });
var buf: [4096]u8 align(8) = undefined;
var builder = capnp.Builder.init(&buf);
builder.initRoot();
const list_start = builder.pos;
stageTocExtract(&builder, pdf_path);
const ptr_offset = builder.ptr_start + capnp.PTR_TOC_ENTRIES * 8;
const list_ptr = std.mem.readInt(u64, buf[ptr_offset..][0..8], .little);
if (!has_outline) {
try std.testing.expectEqual(@as(u64, 0), list_ptr);
try std.testing.expectEqual(list_start, builder.pos);
continue;
}
try std.testing.expect(list_ptr != 0);
const tag = std.mem.readInt(u64, buf[list_start..][0..8], .little);
try std.testing.expectEqual(@as(u32, 3), @as(u32, @truncate(tag)) >> 2);
const titles = [_][]const u8{ "Chapter", "Section", "Appendix" };
const depths = [_]u32{ 0, 1, 0 };
for (titles, depths, 0..) |title, depth, i| {
const elem = list_start + 8 + i * (capnp.TOC_DW + capnp.TOC_PW) * 8;
try std.testing.expectEqual(depth, std.mem.readInt(u32, buf[elem + capnp.TOC_OFF_DEPTH ..][0..4], .little));
const title_ptr_offset = elem + capnp.TOC_DW * 8;
const title_ptr = std.mem.readInt(u64, buf[title_ptr_offset..][0..8], .little);
const title_offset = title_ptr_offset + 8 + ((title_ptr >> 2) & 0x3fffffff) * 8;
try std.testing.expectEqualStrings(title, buf[title_offset .. title_offset + title.len]);
}
}
}
Binary file added ffi/zig/src/testdata/toc.pdf
Binary file not shown.
10 changes: 5 additions & 5 deletions ffi/zig/test/integration_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -247,8 +247,8 @@ test "prefetch hint and done with nonexistent file is safe" {
test "cache init with temp dir" {
var buf: [256]u8 = undefined;
const tmpdir = std.fmt.bufPrintZ(&buf, "/tmp/ddac-test-{d}", .{std.time.milliTimestamp()}) catch return;
std.fs.makeDirAbsolute(std.mem.span(tmpdir)) catch return;
defer std.fs.deleteTreeAbsolute(std.mem.span(tmpdir)) catch {};
std.fs.makeDirAbsolute(tmpdir) catch return;
defer std.fs.deleteTreeAbsolute(tmpdir) catch {};

const handle = ddac_cache_init(tmpdir, 64);
if (handle) |h| {
Expand All @@ -266,8 +266,8 @@ test "cache free null is safe" {
test "cache lookup on empty cache returns miss" {
var buf: [256]u8 = undefined;
const tmpdir = std.fmt.bufPrintZ(&buf, "/tmp/ddac-test-{d}", .{std.time.milliTimestamp()}) catch return;
std.fs.makeDirAbsolute(std.mem.span(tmpdir)) catch return;
defer std.fs.deleteTreeAbsolute(std.mem.span(tmpdir)) catch {};
std.fs.makeDirAbsolute(tmpdir) catch return;
defer std.fs.deleteTreeAbsolute(tmpdir) catch {};

const handle = ddac_cache_init(tmpdir, 64) orelse return;
defer ddac_cache_free(handle);
Expand Down Expand Up @@ -387,7 +387,7 @@ test "conduit process empty file returns empty validation" {
const path = std.fmt.bufPrintZ(&path_buf, "/tmp/ddac-test-empty-{d}", .{std.time.milliTimestamp()}) catch return;
const file = std.fs.createFileAbsoluteZ(path, .{}) catch return;
file.close();
defer std.fs.deleteFileAbsolute(std.mem.span(path)) catch {};
defer std.fs.deleteFileAbsolute(path) catch {};

var result_buf: [88]u8 align(8) = std.mem.zeroes([88]u8);
const rc = ddac_conduit_process(path, @ptrCast(&result_buf));
Expand Down
Loading
Loading