diff --git a/.forgejo/workflows/issue.yml b/.forgejo/workflows/issue.yml new file mode 100644 index 0000000..57cda79 --- /dev/null +++ b/.forgejo/workflows/issue.yml @@ -0,0 +1,14 @@ +on: + issues: + types: [opened, reopened, closed, labeled, edited] + +jobs: + test: + runs-on: self-hosted-nixos-x86_64 + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + - name: Check techtree-manager presence + run: | + test -e ~/.cache/fafo-techtree/techtree-manager + - name: Run techtree-manager + run: cd techtree-manager/ && nix-shell shell.nix --run ~/.cache/fafo-techtree/techtree-manager diff --git a/.forgejo/workflows/push.yml b/.forgejo/workflows/push.yml new file mode 100644 index 0000000..60584c4 --- /dev/null +++ b/.forgejo/workflows/push.yml @@ -0,0 +1,17 @@ +on: + push: + branches: + - 'main' + - 'poc' + +jobs: + build: + runs-on: self-hosted-nixos-x86_64 + steps: + - uses: https://code.forgejo.org/actions/checkout@v4 + - name: Build techtree manager tool + run: cd techtree-manager/ && nix-shell shell.nix --run "cargo build --release" + - name: Cache the techtree manager tool + run: | + mkdir -p ~/.cache/fafo-techtree + cp -v techtree-manager/target/release/techtree-manager ~/.cache/fafo-techtree/techtree-manager diff --git a/README.md b/README.md new file mode 100644 index 0000000..af1bdae --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +FAFO Technology Tree +==================== +This repository tracks our "technology tree" — the steps towards our +overarching goals. + +A description of how this tech tree works can be found below. See +[Working with the Tech Tree](#working-with-the-tech-tree). + +## Full Technology Tree +Below, you can see the full technology tree. The issues in this repository +track the tree's elements. For a better overview, check each issue for a +filtered subtree that contains just the elements related to it. + +![FAFO Tech Tree](https://git.fa-fo.de/fafo/techtree/media/branch/render/techtree.svg) + +## Working with the Tech Tree +Fundamentally, the tech tree is built from the issues in this repository and +their interdependencies. For elements to be reached, create new issues and +select one of the `Type/###` labels to declare what kind it is: + +- Type/**Equipment** 🔬 — A piece of machinery that we need to acquire or get + running. Mainly things that can be bought instead of built. + +- Type/**Process** ⚗️ — A process we need to achieve. This means we need to + become able to perform this process reliably. + +- Type/**Development** 🔩 — A device, software, or other thing we need to develop. + This is _engineering_ work; making use of existing knowledge to build + something useful. + +- Type/**Research** 🧪 — A topic that we can or must research to unlock future + capabilities. In contrast to _Development_ elements, we cannot make use of + prior art here. So less engineering and more _science_. + +#### Dependencies +Once created, add dependencies between issues to model their relationships. +The CI of this repository will automatically update the tech tree accordingly. +In addition, each issue gets a partial representation of the subtree of +elements directly related to it. You can quickly see what is still missing to +achieve a particular element. And also what next steps will be unlocked once +an element has been achieved. + +There are no distinct types of dependencies. This was a choice in the name of +keeping the model simple. All dependencies are hard requirements. But also +check the next section on more thoughts about this... + +#### Modelling Approach +Finding the right balance between model complexity and expressiveness is +tricky. Following are some guidelines for adding elements to this tech tree. + +All elements should have a few fundamental properties, to keep the model consistent: + +- Elements shall have a **clear and unambiguous acceptance criterion**. If + necessary, this can be elaborated on in the issue body. "SEM Imaging" leaves + open what scale we can image reliably. It may be sensible to add multiple + elements to model progress in such a domain. + +- Elements shall be **actionable**, in the sense that someone can put in effort to + achieve them. Having a good acceptance criterion does most of the heavy + lifting here. + +- Elements shall be **necessary** for our bigger visions. Ultimate elements + (elements that nothing depends on) should get special consideration in this + regard. If you have visions of your own, it is of course fine to make an + ultimate element for them. + +- Elements shall have a scope/granularity size that is appropriate for tracking + progress. We will need to figure this out as we go. + +Generally, our tech tree is a living object. It should be updated as we figure +out more elements to be tracked and their dependencies. + +One topic of particular interest are "path choices". We can either use +technology A or technology B to achieve element C: + +```mermaid +flowchart BT + classDef eoi fill:#fff, stroke:#000, color:#000; + classDef dependant fill:#fff, stroke:#888, color:#888; + classDef dep_missing fill:#fcc, stroke:#800, color:#000; + classDef dep_assigned fill:#ffa, stroke:#a50, color:#000; + classDef dep_completed fill:#afa, stroke:#080, color:#000; + 0:::dep_missing + 0["#1 | MISSING
Process
Technology A"] + 1:::dep_completed + 1["#2 | COMPLETED
Process
Technology B"] + 2:::eoi + 2["#3 | MISSING
Process
Element C"] + 2 --> 0 + 2 --> 1 +``` + +Dependencies cannot express such choices. Instead, please follow this approach: + +- Initially, all potential path choice dependencies are added. The issue body + shall document the choice options and implications. + +- When we get closer to the element of interest, we make the choice of what + path to pursue. At this time, the other dependencies are dropped. The + tech-tree now only documents the path choice we have made. + +Another situation we will encounter is the need to use generic equipment for +specific purposes. If the specific purpose is non-trivial, it shall be +modelled as an intermediate _Process_ element in-between: + +```mermaid +flowchart BT + classDef eoi fill:#fff, stroke:#000, color:#000; + classDef dependant fill:#fff, stroke:#888, color:#888; + classDef dep_missing fill:#fcc, stroke:#800, color:#000; + classDef dep_assigned fill:#ffa, stroke:#a50, color:#000; + classDef dep_completed fill:#afa, stroke:#080, color:#000; + 0:::dep_missing + 0["#2 | MISSING
Process
Processing X using machine A"] + 1:::dep_completed + 1["#1 | COMPLETED
Equipment
Generic Machine A"] + 2:::eoi + 2["#3 | MISSING
Process
Element C"] + 2 --> 0 + 0 --> 1 +``` + + + +#### Element Status +The status of each element is determined as follows: + +- **MISSING** when the element has not yet been achieved. +- **ASSIGNED** when the issue has been assigned to someone. This means we are making progress! +- **COMPLETED** when the issue is labelled `Completed`. Achievement unlocked! + +#### Important notes +There are a few gotchas that you should be aware of: + +- The CI will not automatically update when changing dependencies, + unfortunately. You can force a trigger by editing the issue body or quickly + adding and then removing the `Stale` label. + +- Issues that should no longer be a part of the techtree should either be + deleted (looses all tracking) or they can be made inert by closing them. diff --git a/techtree-manager/.gitignore b/techtree-manager/.gitignore new file mode 100644 index 0000000..00df608 --- /dev/null +++ b/techtree-manager/.gitignore @@ -0,0 +1,2 @@ +/target/ +/render-git/ diff --git a/techtree-manager/Cargo.lock b/techtree-manager/Cargo.lock new file mode 100644 index 0000000..2cfb715 --- /dev/null +++ b/techtree-manager/Cargo.lock @@ -0,0 +1,1988 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "async-trait" +version = "0.1.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cc" +version = "1.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "forgejo-api" +version = "0.3.2" +source = "git+https://git.fa-fo.de/rahix/forgejo-api.git?rev=a3f6452cfe774898a89ac66be393e5205f5e12b7#a3f6452cfe774898a89ac66be393e5205f5e12b7" +dependencies = [ + "base64ct", + "bytes", + "reqwest", + "serde", + "serde_json", + "soft_assert", + "thiserror", + "time", + "tokio", + "url", + "zeroize", +] + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "h2" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a194df1107f33c79f4f93d02c80798520551949d59dfad22b6157048a88cca93" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c6e1db7ed32c6c71b759497fae34bf7933636f75a251b9e736555da426f6442" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.172" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "petgraph" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a98c6720655620a521dcc722d0ad66cd8afd5d86e34a89ef691c50b7b24de06" +dependencies = [ + "fixedbitset", + "hashbrown", + "indexmap", + "serde", + "serde_derive", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha256" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f880fc8562bdeb709793f00eb42a2ad0e672c4f883bbe59122b926eca935c8f6" +dependencies = [ + "async-trait", + "bytes", + "hex", + "sha2", + "tokio", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "soft_assert" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5097ec7ea7218135541ad96348f1441d0c616537dd4ed9c47205920c35d7d97" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "syn" +version = "2.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "techtree-manager" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64 0.22.1", + "chrono", + "env_logger", + "forgejo-api", + "log", + "petgraph", + "serde", + "serde_json", + "sha256", + "time", + "tokio", + "url", +] + +[[package]] +name = "tempfile" +version = "3.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "unicase" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/techtree-manager/Cargo.toml b/techtree-manager/Cargo.toml new file mode 100644 index 0000000..bba5bc6 --- /dev/null +++ b/techtree-manager/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "techtree-manager" +version = "0.1.0" +edition = "2024" +authors = ["rahix "] +license = "MIT OR Apache-2.0" +publish = false + +[dependencies] +anyhow = "1.0.98" +base64 = "0.22.1" +chrono = "0.4.41" +env_logger = { version = "0.11.8", default-features = false, features = ["auto-color", "color", "humantime"] } +forgejo-api = { git = "https://git.fa-fo.de/rahix/forgejo-api.git", rev = "a3f6452cfe774898a89ac66be393e5205f5e12b7" } +log = "0.4.27" +petgraph = { version = "0.8.1", features = ["serde-1"] } +serde = { version = "1.0.219", features = ["derive"] } +serde_json = "1.0.140" +sha256 = "1.6.0" +time = "0.3.41" +tokio = { version = "1.45.0", features = ["full"] } +url = "2.5.4" diff --git a/techtree-manager/LICENSE-APACHE b/techtree-manager/LICENSE-APACHE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/techtree-manager/LICENSE-APACHE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/techtree-manager/LICENSE-MIT b/techtree-manager/LICENSE-MIT new file mode 100644 index 0000000..31aa793 --- /dev/null +++ b/techtree-manager/LICENSE-MIT @@ -0,0 +1,23 @@ +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/techtree-manager/shell.nix b/techtree-manager/shell.nix new file mode 100644 index 0000000..c893b75 --- /dev/null +++ b/techtree-manager/shell.nix @@ -0,0 +1,21 @@ +# From 2025-05-21 +let pkgs = import (fetchTarball("https://github.com/NixOS/nixpkgs/archive/36ecfe6216f0aa7f2a1ffe5aafc2c0eae6c8cdcf.tar.gz")) {}; + +in pkgs.mkShell { + buildInputs = [ + # Rust + pkgs.cargo + pkgs.rustc + pkgs.rustfmt + + # Dependencies + pkgs.openssl + pkgs.graphviz + pkgs.git + ]; + + shellHook = '' + export OPENSSL_DIR="${pkgs.openssl.dev}" + export OPENSSL_LIB_DIR="${pkgs.openssl.out}/lib" + ''; +} diff --git a/techtree-manager/src/collect.rs b/techtree-manager/src/collect.rs new file mode 100644 index 0000000..ef3eca1 --- /dev/null +++ b/techtree-manager/src/collect.rs @@ -0,0 +1,156 @@ +use anyhow::Context as _; + +/// Read all issues to generate the full techtree +pub async fn collect_tree(ctx: &crate::Context) -> anyhow::Result { + let mut issues = vec![]; + for page in 1.. { + let new = ctx + .forgejo + .issue_list_issues( + &ctx.owner, + &ctx.repo, + forgejo_api::structs::IssueListIssuesQuery { + // We also want the closed issues + state: Some(forgejo_api::structs::IssueListIssuesQueryState::All), + // We cannot turn off pagination entirely, but let's set the limit as high as + // Forgejo lets us. + limit: Some(10000), + page: Some(page), + // Only issues + r#type: Some(forgejo_api::structs::IssueListIssuesQueryType::Issues), + ..Default::default() + }, + ) + .await + .with_context(|| format!("Failed fetching page {page} of the issue list"))?; + + if new.len() == 0 { + break; + } + + issues.extend(new); + } + + let mut tree = crate::tree::Tree::new(); + let mut issue_numbers = Vec::new(); + + for issue in issues.iter() { + let element = match element_from_issue(issue) { + Ok(el) => el, + Err(e) => { + let maybe_number = issue + .number + .map(|n| n.to_string()) + .unwrap_or("".to_owned()); + log::warn!("Failed processing issue #{maybe_number}: {e:?}"); + continue; + } + }; + + issue_numbers.push(element.issue_number); + tree.add_element(element); + } + + for issue in issue_numbers.into_iter() { + let dependencies = ctx + .forgejo + .issue_list_issue_dependencies( + &ctx.owner, + &ctx.repo, + // Why the hell is the issue number a string here? + &issue.to_string(), + forgejo_api::structs::IssueListIssueDependenciesQuery { + limit: Some(10000), + ..Default::default() + }, + ) + .await + .with_context(|| format!("Failed to fetch issue dependencies for #{issue}",))?; + + for dep in dependencies { + // Check that the dependency is actually an issue in the techtree and not external + let dep_repo = dep + .repository + .context("Dependency issue without repository info")?; + if dep_repo.owner.as_ref() != Some(&ctx.owner) + || dep_repo.name.as_ref() != Some(&ctx.repo) + { + log::warn!( + "Issue #{issue} depends on external {}#{}, ignoring.", + dep_repo.full_name.as_deref().unwrap_or("unknown?"), + dep.number.unwrap_or(9999) + ); + continue; + } + + let dep_number = dep.number.context("Missing issue number in dependency")?; + if !tree.find_element_by_issue_number(dep_number).is_some() { + log::warn!("Found dependency from #{issue} on non-tracked issue #{dep_number}!"); + } else { + tree.add_dependency_by_issue_number(issue, dep_number); + } + } + } + + Ok(tree) +} + +fn element_from_issue(issue: &forgejo_api::structs::Issue) -> anyhow::Result { + let issue_number = issue.number.context("Missing issue number")?; + let description = issue + .title + .as_deref() + .context("Issue is missing a title")? + .to_owned(); + + let labels = issue + .labels + .as_ref() + .context("Issue does not have any labels")?; + + let ty_labels: Vec<_> = labels + .iter() + .filter_map(|l| l.name.as_deref()) + .filter_map(|l| l.strip_prefix("Type/")) + .collect(); + + let ty = match &ty_labels[..] { + [ty] => ty.to_string(), + [] => { + anyhow::bail!("Issue #{issue_number} has no type label!"); + } + [..] => { + anyhow::bail!("Issue #{issue_number} has more than one type label!"); + } + }; + + let has_completed_label = labels + .iter() + .any(|l| l.name.as_deref() == Some("Completed")); + + let status = match issue.state.context("Missing issue state")? { + forgejo_api::structs::StateType::Open => { + if has_completed_label { + crate::tree::ElementStatus::Completed + } else if issue.assignee.is_some() + || issue + .assignees + .as_ref() + .map(|v| v.len() > 0) + .unwrap_or(false) + { + crate::tree::ElementStatus::Assigned + } else { + crate::tree::ElementStatus::Missing + } + } + forgejo_api::structs::StateType::Closed => anyhow::bail!("Ignoring closed issue!"), + }; + + Ok(crate::tree::Element { + issue_number, + description, + ty, + status, + }) +} diff --git a/techtree-manager/src/event_meta.rs b/techtree-manager/src/event_meta.rs new file mode 100644 index 0000000..bcc0d83 --- /dev/null +++ b/techtree-manager/src/event_meta.rs @@ -0,0 +1,57 @@ +use anyhow::Context as _; + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct IssueEventMeta { + pub action: IssueAction, + pub issue: IssueMeta, +} + +#[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq)] +#[serde(rename_all = "lowercase")] +pub enum IssueAction { + Opened, + Reopened, + Closed, + Assigned, + Unassigned, + Edited, + #[serde(rename = "label_updated")] + LabelUpdated, + Labeled, + #[serde(rename = "label_cleared")] + LabelCleared, + Unlabeled, +} + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct IssueMeta { + pub number: u64, + pub repository: RepoMeta, +} + +#[derive(serde::Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct RepoMeta { + pub name: String, + pub owner: String, +} + +pub fn get_issue_event_meta_from_env() -> anyhow::Result { + let path = std::env::var_os("GITHUB_EVENT_PATH") + .context("Could not get event description file path (GITHUB_EVENT_PATH)")?; + let f = std::fs::File::open(path).context("Could not open GITHUB_EVENT_PATH file")?; + let meta: IssueEventMeta = serde_json::de::from_reader(f).context("Failed to parse")?; + Ok(meta) +} + +pub fn fake() -> IssueEventMeta { + IssueEventMeta { + action: IssueAction::Edited, + issue: IssueMeta { + number: 1337, + repository: RepoMeta { + name: "techtree".to_owned(), + owner: "fafo".to_owned(), + }, + }, + } +} diff --git a/techtree-manager/src/issue.rs b/techtree-manager/src/issue.rs new file mode 100644 index 0000000..97cf9d9 --- /dev/null +++ b/techtree-manager/src/issue.rs @@ -0,0 +1,191 @@ +use anyhow::Context as _; + +pub type CommentId = u64; + +pub struct BotCommentInfo { + pub body: String, + pub id: CommentId, +} + +pub async fn make_bot_comment( + ctx: &crate::Context, + issue_number: u64, +) -> anyhow::Result { + let initial_message = + "_Please be patient, this issue is currently being integrated into the techtree..._"; + + let res = ctx + .forgejo + .issue_create_comment( + &ctx.owner, + &ctx.repo, + issue_number, + forgejo_api::structs::CreateIssueCommentOption { + body: initial_message.to_owned(), + updated_at: None, + }, + ) + .await?; + + Ok(BotCommentInfo { + id: res.id.context("Missing id for the bot comment")?, + body: initial_message.to_owned(), + }) +} + +pub async fn find_bot_comment( + ctx: &crate::Context, + issue_number: u64, +) -> anyhow::Result> { + let mut comments = ctx + .forgejo + .issue_get_comments( + &ctx.owner, + &ctx.repo, + issue_number, + forgejo_api::structs::IssueGetCommentsQuery { + ..Default::default() + }, + ) + .await + .context("Failed fetching comments for issue")?; + + comments.sort_by_key(|comment| comment.created_at); + + let maybe_bot_comment = comments + .iter() + .rev() + .find(|comment| comment.user.as_ref().and_then(|u| u.id) == Some(-2)); + + Ok(maybe_bot_comment + .map(|c| -> anyhow::Result<_> { + Ok(BotCommentInfo { + body: c.body.clone().unwrap_or("".to_owned()), + id: c.id.context("Missing id for the bot comment")?, + }) + }) + .transpose()?) +} + +/// Find existing bot comment or create a new one. +/// +/// Returns a tuple of the comment information and a boolean indicating whether the comment was +/// newly created. +pub async fn find_or_make_bot_comment( + ctx: &crate::Context, + issue_number: u64, +) -> anyhow::Result<(BotCommentInfo, bool)> { + if let Some(comment) = find_bot_comment(ctx, issue_number) + .await + .context("Failed to search for bot comment in issue")? + { + Ok((comment, false)) + } else { + make_bot_comment(ctx, issue_number) + .await + .context("Failed to make new bot comment in issue") + .map(|c| (c, true)) + } +} + +pub async fn update_bot_comment( + ctx: &crate::Context, + id: CommentId, + new_body: String, +) -> anyhow::Result<()> { + ctx.forgejo + .issue_edit_comment( + &ctx.owner, + &ctx.repo, + id, + forgejo_api::structs::EditIssueCommentOption { + body: new_body, + updated_at: None, + }, + ) + .await + .context("Failed to update comment body")?; + + Ok(()) +} + +pub async fn update_bot_comment_from_subtree( + ctx: &crate::Context, + id: CommentId, + subtree: &crate::tree::Subtree<'_>, + hash: &str, +) -> anyhow::Result<()> { + let mut mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), false); + + // When the mermaid graph gets too big, regenerate a simplified version. + if mermaid.len() > 4990 { + log::info!("Mermaid graph is too big, generating simplified version..."); + mermaid = subtree.to_mermaid(&ctx.repo_url.to_string(), true); + } + + let full_text = if mermaid.len() > 4990 { + format!( + r##"## Partial Techtree +_Sorry, the partial techtree is still too big to be displayed for this element..._ + +Digest: {hash}; Last Updated: {timestamp}"##, + timestamp = ctx.timestamp, + ) + } else { + format!( + r##"## Partial Techtree +```mermaid +{mermaid} +``` + +Digest: {hash}; Last Updated: {timestamp}"##, + timestamp = ctx.timestamp, + ) + }; + + update_bot_comment(&ctx, id, full_text).await?; + + Ok(()) +} + +pub async fn remove_stale_label(ctx: &crate::Context, issue_number: u64) -> anyhow::Result<()> { + let labels = ctx + .forgejo + .issue_get_labels(&ctx.owner, &ctx.repo, issue_number) + .await + .context("Failed fetching issue labels")?; + + let stale_label_id = labels + .iter() + .filter(|l| l.name.as_deref() == Some("Stale")) + .next() + .map(|l| l.id.ok_or(anyhow::anyhow!("`Stale` label has no ID"))) + .transpose()?; + + if let Some(stale_label_id) = stale_label_id { + log::info!("Removing `Stale` label from issue #{issue_number}..."); + + let res = ctx + .forgejo + .issue_remove_label( + &ctx.owner, + &ctx.repo, + issue_number, + stale_label_id, + forgejo_api::structs::DeleteLabelsOption { + updated_at: Some(time::OffsetDateTime::now_utc()), + }, + ) + .await; + + if let Err(e) = res { + // At the moment, the token for Forgejo Actions cannot remove issue labels. + // See https://codeberg.org/forgejo/forgejo/issues/2415 + log::warn!( + "Failed to remove `Stale` label for #{issue_number}. This is a known Forgejo limitation at the moment... ({e})" + ); + } + } + + Ok(()) +} diff --git a/techtree-manager/src/main.rs b/techtree-manager/src/main.rs new file mode 100644 index 0000000..eda6053 --- /dev/null +++ b/techtree-manager/src/main.rs @@ -0,0 +1,181 @@ +#![allow(dead_code)] + +use anyhow::Context as _; +use forgejo_api::Forgejo; + +mod collect; +mod event_meta; +mod issue; +mod render; +mod tree; +mod wiki; + +pub struct Context { + /// API Accessor object + pub forgejo: forgejo_api::Forgejo, + /// Repository Owner + pub owner: String, + /// Repository Name + pub repo: String, + /// URL of the repository page + pub repo_url: url::Url, + /// URL of the repository with authentication information attached + pub repo_auth_url: Option, + /// Human readable timestamp of this manager run + pub timestamp: String, +} + +async fn run() -> anyhow::Result<()> { + let meta = if std::env::var("TECHTREE_FAKE").ok().is_some() { + log::warn!("Fake tree!"); + event_meta::fake() + } else { + event_meta::get_issue_event_meta_from_env() + .context("Maybe you want to run with TECHTREE_FAKE=1?") + .context("Failed reading issue event data")? + }; + + log::info!( + "Running due to event \"{:?}\" on issue #{} ...", + meta.action, + meta.issue.number + ); + + let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S").to_string(); + log::info!("Timestamp of this run is {timestamp}"); + + let token = std::env::var("GITHUB_TOKEN").ok(); + if token.is_none() { + log::warn!("No GITHUB_TOKEN, only performing read-only operations!"); + } + + let server_url = url::Url::parse(&std::env::var("GITHUB_SERVER_URL").unwrap_or_else(|_e| { + log::warn!("Using FAFO URL as a default GITHUB_SERVER_URL!"); + "https://git.fa-fo.de".to_string() + })) + .context("Failed parsing GITHUB_SERVER_URL as a url")?; + + let repo_url = server_url + .join(&format!( + "{}/{}", + meta.issue.repository.owner, meta.issue.repository.name + )) + .with_context(|| { + format!("failed building repository URL from GITHUB_SERVER_URL: {server_url}") + })?; + + let repo_auth_url = token + .as_ref() + .map(|token| -> Result<_, ()> { + let mut repo_auth_url = repo_url.clone(); + repo_auth_url.set_username("forgejo-actions")?; + repo_auth_url.set_password(Some(&token))?; + Ok(repo_auth_url) + }) + .transpose() + .map_err(|_e| anyhow::anyhow!("Repo URL does not have correct format")) + .context("Failed adding auth info to repo URL")?; + + let auth = if let Some(token) = token.as_ref() { + forgejo_api::Auth::Token(token) + } else { + forgejo_api::Auth::None + }; + let forgejo = + Forgejo::new(auth, server_url).context("Could not create Forgejo API access object")?; + + let ctx = Context { + forgejo, + owner: meta.issue.repository.owner.clone(), + repo: meta.issue.repository.name.clone(), + repo_url, + repo_auth_url, + timestamp, + }; + + if meta.action == event_meta::IssueAction::Opened { + log::debug!("Running for a newly opened issue. Taking care of the comment first..."); + match issue::find_or_make_bot_comment(&ctx, meta.issue.number).await { + Ok((_comment, is_new)) => { + if is_new { + log::info!( + "Waiting for a minute, as the issue is brand new. We will likely catch some more updates this way!" + ); + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + } + } + Err(err) => { + log::warn!( + "Error while commenting on new issue #{}, continuing anyway... Error: {err:?}", + meta.issue.number + ); + } + } + } + + log::info!("Collecting tree from issue metadata..."); + let tree = collect::collect_tree(&ctx) + .await + .context("Failed to collect the techtree from issue metadata")?; + + log::info!("Rendering and publishing techtree to git repository..."); + let rendered_tree = render::render(&ctx, &tree) + .await + .context("Failed to render the techtree")?; + + if token.is_some() { + render::publish(&ctx, &rendered_tree) + .await + .context("Failed to publish rendered tree to git")?; + } else { + log::warn!("Skipped publishing the rendered tree."); + } + + // Wiki is disabled because the tree is too big for mermaid to handle + if false { + log::info!("Updating the wiki overview..."); + wiki::update_wiki_from_tree(&ctx, &tree) + .await + .context("Failed to update the techtree wiki page")?; + } + + if token.is_none() { + log::warn!("Not running issue updates without token."); + return Ok(()); + } + + 'issues: for issue in tree.iter_issues() { + let subtree = tree.subtree_for_issue(issue).expect("issue from tree not found in tree"); + let hash = subtree.stable_hash(); + + let (bot_comment, _is_new) = issue::find_or_make_bot_comment(&ctx, issue) + .await + .with_context(|| format!("Failed to find or make bot comment in issue #{issue}"))?; + + if bot_comment.body.contains(&hash) { + log::debug!("Issue #{issue} is up to date, not editing comment."); + issue::remove_stale_label(&ctx, issue) + .await + .with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?; + + continue 'issues; + } + + log::info!("Updating bot comment in issue #{issue} ..."); + issue::update_bot_comment_from_subtree(&ctx, bot_comment.id, &subtree, &hash) + .await + .with_context(|| format!("Failed to update bot comment in issue #{issue}"))?; + + issue::remove_stale_label(&ctx, issue) + .await + .with_context(|| format!("Failed to remove `Stale` label from issue #{issue}"))?; + } + + Ok(()) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + run().await +} diff --git a/techtree-manager/src/render.rs b/techtree-manager/src/render.rs new file mode 100644 index 0000000..f18bebb --- /dev/null +++ b/techtree-manager/src/render.rs @@ -0,0 +1,128 @@ +use std::process::Command; + +use anyhow::Context as _; + +trait SuccessExt { + fn success(&mut self) -> anyhow::Result<()>; +} + +impl SuccessExt for Command { + fn success(&mut self) -> anyhow::Result<()> { + if !self.status()?.success() { + anyhow::bail!("Command returned unsuccessfully"); + } + Ok(()) + } +} + +pub struct RenderedTree { + pub repo_dir: std::path::PathBuf, + pub dot_file_name: std::ffi::OsString, + pub svg_file_name: std::ffi::OsString, +} + +impl RenderedTree { + pub fn dot_file(&self) -> std::path::PathBuf { + self.repo_dir.join(&self.dot_file_name) + } + + pub fn svg_file(&self) -> std::path::PathBuf { + self.repo_dir.join(&self.svg_file_name) + } +} + +pub async fn render( + _ctx: &crate::Context, + tree: &crate::tree::Tree, +) -> anyhow::Result { + let repo_dir = std::path::PathBuf::from("render-git"); + + let info = RenderedTree { + repo_dir, + dot_file_name: "techtree.dot".into(), + svg_file_name: "techtree.svg".into(), + }; + + if info.repo_dir.is_dir() { + log::info!("Found old {:?} repository, removing...", info.repo_dir); + std::fs::remove_dir_all(&info.repo_dir) + .context("Failed to remove stale render repository")?; + } + + std::fs::create_dir(&info.repo_dir).context("Failed creating directory for rendered graph")?; + + let dot_source = tree.to_dot(); + std::fs::write(&info.dot_file(), dot_source.as_bytes()) + .context("Failed to write `dot` source file")?; + + Command::new("dot") + .args(["-T", "svg"]) + .arg("-o") + .arg(&info.svg_file()) + .arg(&info.dot_file()) + .success() + .context("Failed to generate svg graph from dot source")?; + + Ok(info) +} + +pub async fn publish(ctx: &crate::Context, rendered: &RenderedTree) -> anyhow::Result<()> { + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .arg("init") + .arg("--initial-branch=render") + .arg("--quiet") + .success() + .context("Failed to initialize render repository")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .args(["config", "user.email", "git@fa-fo.de"]) + .success() + .context("Failed to configure identity for render repo")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .args(["config", "user.name", "Forgejo Actions"]) + .success() + .context("Failed to configure identity for render repo")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .arg("add") + .arg(&rendered.dot_file_name) + .arg(&rendered.svg_file_name) + .success() + .context("Failed to add generated graph files to git index")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .arg("commit") + .arg("--quiet") + .args(["-m", &format!("Updated techtree at {}", ctx.timestamp)]) + .success() + .context("Failed to add generated graph files to git index")?; + + Command::new("git") + .arg("-C") + .arg(&rendered.repo_dir) + .arg("push") + .arg("--force") + .arg("--quiet") + .arg( + ctx.repo_auth_url + .as_ref() + .expect("cannot publish without authentication token") + .to_string(), + ) + .arg("HEAD:refs/heads/render") + .status() + .context("Failed to push rendered graph to forgejo repository")?; + + Ok(()) +} diff --git a/techtree-manager/src/tree.rs b/techtree-manager/src/tree.rs new file mode 100644 index 0000000..3ec271f --- /dev/null +++ b/techtree-manager/src/tree.rs @@ -0,0 +1,433 @@ +use std::collections::BTreeMap; + +use petgraph::visit::EdgeRef as _; + +const HASH_EPOCH: u32 = 0x00000001; + +/// Element in the techtree +#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize)] +pub struct Element { + /// Issue associated with this element + pub issue_number: u64, + /// Description of this element + pub description: String, + /// Type of this element + pub ty: String, + /// Completion status of this element. + pub status: ElementStatus, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +pub enum ElementRole { + Root, + Ultimate, + Intermediate, + Disjoint, +} + +impl Element { + fn to_dot_node_attributes( + &self, + role: Option, + subtree_role: Option, + ) -> String { + let Element { + issue_number, + description, + ty, + status, + } = self; + + let mut attributes = Vec::new(); + + let description = description + .replace("<", "<") + .replace(">", ">") + .replace("&", "&") + .replace("'", "'") + .replace("\"", """); + + attributes.push(format!( + r##"label = <{{{{#{issue_number} | {status}}}|{ty}|{description}}}>"## + )); + attributes.push(r#"shape = "record""#.to_owned()); + + let (color, background) = match (subtree_role, status) { + (Some(SubtreeElementRole::ElementOfInterest), _) => ("black", "white"), + (Some(SubtreeElementRole::Dependant), _) => ("gray", "gray"), + (_, ElementStatus::Missing) => ( + "#800", + // Highlight root elements + if role == Some(ElementRole::Root) { + "#ffddc1" + } else { + "#fcc" + }, + ), + (_, ElementStatus::Assigned) => ("#a50", "#ffa"), + (_, ElementStatus::Completed) => ("#080", "#afa"), + }; + attributes.push(format!(r#"color = "{color}""#)); + attributes.push(format!(r#"fontcolor = "{color}""#)); + attributes.push(format!(r#"fillcolor = "{background}""#)); + attributes.push(format!(r#"style = "filled""#)); + + attributes.join(", ") + } + + fn to_mermaid_node( + &self, + index: ElementIndex, + role: Option, + repo_url: &str, + simple: bool, + ) -> String { + let Element { + issue_number, + description, + ty, + status, + } = self; + + let label = if simple { + format!(r##"#{issue_number} | {status}
{ty}
{description}"##) + } else { + format!( + r##"#{issue_number} | {status}
{ty}
{description}"## + ) + }; + + let class = match (role, status) { + (Some(SubtreeElementRole::ElementOfInterest), _) => "eoi", + (Some(SubtreeElementRole::Dependant), _) => "dependant", + (_, ElementStatus::Missing) => "dep_missing", + (_, ElementStatus::Assigned) => "dep_assigned", + (_, ElementStatus::Completed) => "dep_completed", + }; + + format!( + " {index}:::{class}\n {index}[\"{label}\"]", + index = index.index(), + ) + } +} + +fn mermaid_classes() -> String { + r##" + classDef eoi fill:#fff, stroke:#000, color:#000; + classDef dependant fill:#fff, stroke:#888, color:#888; + classDef dep_missing fill:#fcc, stroke:#800, color:#000; + classDef dep_assigned fill:#ffa, stroke:#a50, color:#000; + classDef dep_completed fill:#afa, stroke:#080, color:#000; +"## + .to_owned() +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize)] +pub enum ElementStatus { + Missing, + Assigned, + Completed, +} + +impl std::fmt::Display for ElementStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ElementStatus::Missing => "MISSING", + ElementStatus::Assigned => "ASSIGNED", + ElementStatus::Completed => "COMPLETED", + }) + } +} + +pub type ElementIndex = petgraph::graph::NodeIndex; + +#[derive(serde::Serialize)] +pub struct Tree { + graph: petgraph::Graph, + #[serde(skip)] + issue_map: BTreeMap, +} + +impl Tree { + pub fn new() -> Self { + Self { + graph: petgraph::Graph::new(), + issue_map: BTreeMap::new(), + } + } + + pub fn add_element(&mut self, element: Element) { + let issue_number = element.issue_number; + assert!(!self.issue_map.contains_key(&issue_number)); + let idx = self.graph.add_node(element); + self.issue_map.insert(issue_number, idx); + } + + pub fn add_dependency_by_issue_number(&mut self, dependant: u64, dependency: u64) { + let a = self + .find_element_by_issue_number(dependant) + .expect("dependant element does not exist"); + let b = self + .find_element_by_issue_number(dependency) + .expect("dependency element does not exist"); + self.graph.add_edge(a, b, ()); + } + + pub fn find_element_by_issue_number(&self, issue_number: u64) -> Option { + self.issue_map.get(&issue_number).copied() + } + + pub fn subtree_for_issue<'a>(&'a self, issue_number: u64) -> Option> { + Some(Subtree::new_for_element( + self, + self.find_element_by_issue_number(issue_number)?, + )) + } + + pub fn iter_issues<'a>(&'a self) -> impl Iterator + 'a { + self.issue_map.keys().copied() + } + + pub fn subtree_for_element<'a>(&'a self, element: ElementIndex) -> Subtree<'a> { + Subtree::new_for_element(self, element) + } + + pub fn iter_ultimate_elements<'a>(&'a self) -> impl Iterator + 'a { + self.graph.node_indices().filter(|index| { + // If there are no incoming edges, then this is an ultimate element! + self.graph + .neighbors_directed(*index, petgraph::Direction::Incoming) + .next() + .is_none() + }) + } + + pub fn get_element_role(&self, element: ElementIndex) -> ElementRole { + let has_dependencies = self + .graph + .neighbors_directed(element, petgraph::Direction::Outgoing) + .next() + .is_some(); + let has_dependants = self + .graph + .neighbors_directed(element, petgraph::Direction::Incoming) + .next() + .is_some(); + + match (has_dependencies, has_dependants) { + (false, true) => ElementRole::Root, + (true, false) => ElementRole::Ultimate, + (true, true) => ElementRole::Intermediate, + (false, false) => ElementRole::Disjoint, + } + } + + pub fn to_dot(&self) -> String { + let to_node_attributes = |_g, (id, element): (ElementIndex, &Element)| { + element.to_dot_node_attributes(Some(self.get_element_role(id)), None) + }; + let dot = petgraph::dot::Dot::with_attr_getters( + &self.graph, + &[ + petgraph::dot::Config::EdgeNoLabel, + petgraph::dot::Config::NodeNoLabel, + petgraph::dot::Config::RankDir(petgraph::dot::RankDir::BT), + petgraph::dot::Config::GraphContentOnly, + ], + &|_g, _edge_id| "".to_string(), + &to_node_attributes, + ); + + let ultimate_elements: Vec<_> = self + .iter_ultimate_elements() + .map(|idx| idx.index().to_string()) + .collect(); + let ultimate_element_list = ultimate_elements.join("; "); + + format!( + r#"digraph {{ + ranksep=1.2 + {{ rank=same; {ultimate_element_list}; }} +{:?} +}} +"#, + dot + ) + } + + pub fn to_mermaid(&self, repo_url: &str, simple: bool) -> String { + let mut mermaid = String::new(); + mermaid.push_str("flowchart BT\n"); + mermaid.push_str(&mermaid_classes()); + + for index in self.graph.node_indices() { + mermaid.push_str(&self.graph[index].to_mermaid_node(index, None, repo_url, simple)); + mermaid.push_str("\n"); + } + + for edge in self.graph.edge_references() { + mermaid.push_str(&format!( + " {} --> {}\n", + edge.source().index(), + edge.target().index() + )); + } + + mermaid + } +} + +#[derive(Debug, Clone, Hash, PartialEq, Eq, serde::Serialize)] +pub struct SubtreeElement<'a> { + element: &'a Element, + role: SubtreeElementRole, +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, serde::Serialize)] +pub enum SubtreeElementRole { + ElementOfInterest, + Dependency, + Dependant, +} + +pub struct Subtree<'a> { + original: &'a Tree, + graph: petgraph::Graph, ()>, + index_map: BTreeMap, +} + +impl<'a> Subtree<'a> { + pub fn new_for_element(tree: &'a Tree, element: ElementIndex) -> Subtree<'a> { + let mut this = Self { + original: tree, + graph: petgraph::Graph::new(), + index_map: BTreeMap::new(), + }; + let graph = &this.original.graph; + + this.add_node_if_missing(element, SubtreeElementRole::ElementOfInterest); + + let mut dfs = petgraph::visit::Dfs::new(&graph, element); + while let Some(idx) = dfs.next(&graph) { + this.add_node_if_missing(idx, SubtreeElementRole::Dependency); + } + + for idx in this.index_map.keys().copied() { + for neighbor in graph.neighbors_directed(idx, petgraph::Direction::Outgoing) { + if !this.index_map.contains_key(&neighbor) { + log::warn!( + "Probably missed a dependency from #{from} to #{to} while generating subtree for #{el}", + el = graph[element].issue_number, + from = graph[idx].issue_number, + to = graph[neighbor].issue_number + ); + continue; + } + + this.graph + .add_edge(this.index_map[&idx], this.index_map[&neighbor], ()); + } + } + + for neighbor in graph.neighbors_directed(element, petgraph::Direction::Incoming) { + if this.index_map.contains_key(&neighbor) { + log::warn!( + "Already found #{from} in dependencies of #{to}, but it should have been the other way around?!", + from = graph[neighbor].issue_number, + to = graph[element].issue_number + ); + } else { + this.add_node_if_missing(neighbor, SubtreeElementRole::Dependant); + } + + this.graph + .add_edge(this.index_map[&neighbor], this.index_map[&element], ()); + } + + this + } + + fn add_node_if_missing(&mut self, index: ElementIndex, role: SubtreeElementRole) -> bool { + if self.index_map.contains_key(&index) { + debug_assert!(self.graph.node_weight(self.index_map[&index]).is_some()); + return false; + } + + let subtree_element = SubtreeElement { + element: &self.original.graph[index], + role, + }; + + let new_idx = self.graph.add_node(subtree_element); + self.index_map.insert(index, new_idx); + + return true; + } + + pub fn to_dot(&self) -> String { + let dot = petgraph::dot::Dot::with_attr_getters( + &self.graph, + &[ + petgraph::dot::Config::EdgeNoLabel, + petgraph::dot::Config::NodeNoLabel, + petgraph::dot::Config::RankDir(petgraph::dot::RankDir::BT), + ], + &|_g, _edge_id| "".to_string(), + &|_g, (_, element)| { + element + .element + .to_dot_node_attributes(None, Some(element.role)) + }, + ); + + format!("{:?}", dot) + } + + pub fn to_mermaid(&self, repo_url: &str, simple: bool) -> String { + let mut mermaid = String::new(); + mermaid.push_str("flowchart BT\n"); + mermaid.push_str(&mermaid_classes()); + + for index in self.graph.node_indices() { + let node = &self.graph[index]; + mermaid.push_str(&node.element.to_mermaid_node( + index, + Some(node.role), + repo_url, + simple, + )); + mermaid.push_str("\n"); + } + + for edge in self.graph.edge_references() { + mermaid.push_str(&format!( + " {} --> {}\n", + edge.source().index(), + edge.target().index() + )); + } + + mermaid + } + + pub fn stable_hash(&self) -> String { + let mut nodes: Vec<_> = self.graph.node_weights().collect(); + nodes.sort_by_key(|n| n.element.issue_number); + let mut edges: Vec<_> = self + .graph + .edge_references() + .map(|edge| { + ( + self.graph[edge.source()].element.issue_number, + self.graph[edge.target()].element.issue_number, + ) + }) + .collect(); + edges.sort(); + + let json_data = serde_json::ser::to_string(&(nodes, edges, HASH_EPOCH)) + .expect("serialization for a stable hash failed"); + sha256::digest(json_data) + } +} diff --git a/techtree-manager/src/wiki.rs b/techtree-manager/src/wiki.rs new file mode 100644 index 0000000..e61f931 --- /dev/null +++ b/techtree-manager/src/wiki.rs @@ -0,0 +1,40 @@ +use anyhow::Context as _; +use base64::prelude::*; + +pub async fn update_wiki_from_tree( + ctx: &crate::Context, + tree: &crate::tree::Tree, +) -> anyhow::Result<()> { + let mermaid = tree.to_mermaid(&ctx.repo_url.to_string(), false); + let wiki_text = format!( + r##"This page is automatically updated to show the latest and greatest FAFO techtree: + +```mermaid +{mermaid} +``` +"## + ); + update_wiki_overview(&ctx, wiki_text) + .await + .context("Failed to update the techtree wiki page")?; + Ok(()) +} + +pub async fn update_wiki_overview(ctx: &crate::Context, new_body: String) -> anyhow::Result<()> { + // TODO: Figure out why we get a 404 when the edit was successfull... + let _ = ctx + .forgejo + .repo_edit_wiki_page( + &ctx.owner, + &ctx.repo, + "Home", + forgejo_api::structs::CreateWikiPageOptions { + content_base64: Some(BASE64_STANDARD.encode(new_body.as_bytes())), + message: Some(format!("Updated to latest model at {}", ctx.timestamp)), + title: Some("Home".to_owned()), + }, + ) + .await + .context("Failed editing the wiki page"); + Ok(()) +} diff --git a/techtree.dot b/techtree.dot deleted file mode 100644 index 18151c7..0000000 --- a/techtree.dot +++ /dev/null @@ -1,278 +0,0 @@ -digraph { - ranksep=1.2 - { rank=same; 0; 1; 2; 3; 4; 9; 25; 50; } - rankdir="BT" - 0 [ label = <{{#115 | MISSING}|Process|Semiconductor Manufacturing}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 1 [ label = <{{#114 | MISSING}|Process|Semiconductor & Electronics Analysis}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 2 [ label = <{{#113 | MISSING}|Process|Science Communication}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 3 [ label = <{{#112 | MISSING}|Process|Nanoscale Imaging}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 4 [ label = <{{#111 | MISSING}|Process|LIN supply}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 5 [ label = <{{#110 | ASSIGNED}|Equipment|Local NAS/server}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 6 [ label = <{{#109 | MISSING}|Equipment|Environmental Chamber}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 7 [ label = <{{#108 | MISSING}|Development|Piezo Driver}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 8 [ label = <{{#107 | MISSING}|Process|EMI Precompliance Testing}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 9 [ label = <{{#106 | MISSING}|Process|Electronics Distribution}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 10 [ label = <{{#105 | ASSIGNED}|Process|Create a Backlit LCD Device}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 11 [ label = <{{#104 | ASSIGNED}|Process|Electroluminescence Foil}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 12 [ label = <{{#103 | MISSING}|Process|Create a Matrix LCD Device}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 13 [ label = <{{#102 | ASSIGNED}|Research|Acquire Liquid Crystal Fluid}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 14 [ label = <{{#101 | MISSING}|Process|Chemical Vapor Deposition}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 15 [ label = <{{#100 | MISSING}|Process|Etch fractals on wafers}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 16 [ label = <{{#99 | ASSIGNED}|Process|Create an LCD Device}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 17 [ label = <{{#98 | ASSIGNED}|Development|Gridfinity-compatible JEDEC IC holders}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 18 [ label = <{{#97 | COMPLETED}|Equipment|PCB Rework Microscope}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 19 [ label = <{{#96 | COMPLETED}|Equipment|Soldering Station}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 20 [ label = <{{#95 | MISSING}|Equipment|Thermal Camera}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 21 [ label = <{{#94 | ASSIGNED}|Process|PCB Reverse Engineering}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 22 [ label = <{{#93 | COMPLETED}|Equipment|HEPA Fume Extractor}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 23 [ label = <{{#92 | COMPLETED}|Equipment|Miscellaneous Tools for BGA Rework}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 24 [ label = <{{#91 | COMPLETED}|Equipment|Hot Air Rework Station}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 25 [ label = <{{#90 | ASSIGNED}|Process|PCB Repair & Rework}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 26 [ label = <{{#89 | ASSIGNED}|Process|PCB Delayering}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 27 [ label = <{{#88 | MISSING}|Development|Piezo Slider Actuators}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 28 [ label = <{{#87 | MISSING}|Equipment|Diode Laser}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 29 [ label = <{{#86 | ASSIGNED}|Development|Optomechanical System}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 30 [ label = <{{#85 | ASSIGNED}|Development|Laser Diode Driver/Controller}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 31 [ label = <{{#84 | MISSING}|Equipment|Stabilized Helium-Neon Laser}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 32 [ label = <{{#83 | MISSING}|Equipment|External Cavity Diode Laser (ECDL)}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 33 [ label = <{{#82 | MISSING}|Equipment|Coherent Light Source}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 34 [ label = <{{#81 | MISSING}|Process|Interferometry}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 35 [ label = <{{#80 | MISSING}|Process|Plasma Cleaning}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 36 [ label = <{{#79 | MISSING}|Equipment|Sputter Coater}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 37 [ label = <{{#78 | ASSIGNED}|Process|Optical Die Imaging}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 38 [ label = <{{#77 | MISSING}|Process|Imaging of Human Blood Cells}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 39 [ label = <{{#76 | COMPLETED}|Process|Video Streaming}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 40 [ label = <{{#75 | COMPLETED}|Equipment|Microphone(s)}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 41 [ label = <{{#74 | COMPLETED}|Equipment|Camcorder}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 42 [ label = <{{#73 | COMPLETED}|Development|Main Camera Rig}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 43 [ label = <{{#72 | MISSING}|Process|STM Non-metallic imaging}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 44 [ label = <{{#71 | MISSING}|Research|STM Metal Pattern Deposition}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 45 [ label = <{{#70 | MISSING}|Research|STM Lithography}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 46 [ label = <{{#69 | MISSING}|Research|STM Surface Topography Manipulation}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 47 [ label = <{{#68 | ASSIGNED}|Equipment|Heating Stirrer}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 48 [ label = <{{#67 | ASSIGNED}|Equipment|Chemical Storage/Containment}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 49 [ label = <{{#66 | COMPLETED}|Process|Video Recording}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 50 [ label = <{{#65 | MISSING}|Process|Gas chromatography}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 51 [ label = <{{#64 | COMPLETED}|Equipment|FFF 3D-Printer}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 52 [ label = <{{#63 | ASSIGNED}|Equipment|OpenFlexure Delta Stage}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 53 [ label = <{{#62 | MISSING}|Process|Lithography}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 54 [ label = <{{#61 | MISSING}|Process|Zeloof Z1 process}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 55 [ label = <{{#60 | MISSING}|Process|Thermal diffusion doping}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 56 [ label = <{{#59 | MISSING}|Process|H₃PO₄/HNO₃/AcOH Etching of Aluminum}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 57 [ label = <{{#58 | MISSING}|Process|Pattern Al}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 58 [ label = <{{#57 | MISSING}|Process|Metal thin film deposition}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 59 [ label = <{{#56 | MISSING}|Process|PVD: Thermal Evaporation}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 60 [ label = <{{#55 | ASSIGNED}|Equipment|Tube Furnace}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 61 [ label = <{{#54 | MISSING}|Process|Si Thermal Oxidation}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 62 [ label = <{{#53 | MISSING}|Process|Pattern SiO₂}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 63 [ label = <{{#52 | MISSING}|Development|Maskless photolithography stepper}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 64 [ label = <{{#51 | MISSING}|Process|Maskless Photolithography}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 65 [ label = <{{#50 | MISSING}|Process|E-beam Lithography}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 66 [ label = <{{#49 | ASSIGNED}|Development|Spin Coater}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 67 [ label = <{{#48 | MISSING}|Process|STM Imaging Individual Atoms}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 68 [ label = <{{#47 | MISSING}|Process|STM Imaging@1nm}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 69 [ label = <{{#46 | MISSING}|Process|STM Imaging@10nm}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 70 [ label = <{{#45 | ASSIGNED}|Process|STM Imaging@100nm}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 71 [ label = <{{#44 | ASSIGNED}|Process|STM Imaging@1µm}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 72 [ label = <{{#43 | ASSIGNED}|Development|Scanning Tunneling Microscope (STM)}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 73 [ label = <{{#42 | COMPLETED}|Development|STM Vibration Isolation}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 74 [ label = <{{#41 | COMPLETED}|Process|Primitive Die Imaging}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 75 [ label = <{{#39 | ASSIGNED}|Development|OBI Lite}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 76 [ label = <{{#38 | MISSING}|Process|SEM Non-Metallic Imaging}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 77 [ label = <{{#37 | MISSING}|Process|PVD: Sputtering}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 78 [ label = <{{#36 | MISSING}|Process|Primitive Chip Imaging}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 79 [ label = <{{#35 | MISSING}|Equipment|SEM Beam Blanker}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 80 [ label = <{{#34 | COMPLETED}|Equipment|Open Beam Interface}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 81 [ label = <{{#33 | COMPLETED}|Equipment|SEM Vibration Isolation}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 82 [ label = <{{#32 | MISSING}|Process|SEM Imaging@10nm}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 83 [ label = <{{#31 | COMPLETED}|Process|SEM Imaging@100nm}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 84 [ label = <{{#30 | COMPLETED}|Process|SEM Imaging@1µm}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 85 [ label = <{{#29 | COMPLETED}|Process|SEM Imaging@10µm}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 86 [ label = <{{#28 | COMPLETED}|Equipment|Scanning Electron Microscope (SEM)}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 87 [ label = <{{#27 | ASSIGNED}|Equipment|Technical Ventilation}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 88 [ label = <{{#26 | COMPLETED}|Equipment|Water Cooling}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 89 [ label = <{{#25 | MISSING}|Equipment|Plasma Etcher}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 90 [ label = <{{#24 | COMPLETED}|Equipment|~3ph Power (below 10kW)}>, shape = "record", color = "#080", fontcolor = "#080", fillcolor = "#afa", style = "filled"] - 91 [ label = <{{#23 | ASSIGNED}|Equipment|Fume Hood}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 92 [ label = <{{#21 | MISSING}|Process|Reactive Ion Etching}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 93 [ label = <{{#20 | MISSING}|Process|Body Bias Injection}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 94 [ label = <{{#19 | MISSING}|Process|IC Fault Injection}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 95 [ label = <{{#18 | MISSING}|Research|SEM Fault Injection}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 96 [ label = <{{#17 | MISSING}|Process|SEM Nanoprobing/Live Analysis}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 97 [ label = <{{#16 | MISSING}|Process|Laser Fault Injection}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 98 [ label = <{{#15 | MISSING}|Process|Microprobing}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 99 [ label = <{{#14 | ASSIGNED}|Equipment|Optical Microscope}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 100 [ label = <{{#13 | MISSING}|Process|Full Chip Reverse Engineering}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 101 [ label = <{{#12 | MISSING}|Process|DASH Stain}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 102 [ label = <{{#11 | MISSING}|Process|Simple Chip Imaging}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 103 [ label = <{{#10 | ASSIGNED}|Equipment|SEM: Motorized Stage}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 104 [ label = <{{#9 | ASSIGNED}|Development|Optical Microscope: Motorized Stage}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 105 [ label = <{{#8 | MISSING}|Process|Plasma Etching}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 106 [ label = <{{#7 | MISSING}|Process|Laser Cutting}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 107 [ label = <{{#6 | MISSING}|Process|HNO₃/H₂SO₄ Decapping}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 108 [ label = <{{#5 | MISSING}|Process|BOE Etching of SiO₂}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 109 [ label = <{{#4 | ASSIGNED}|Equipment|Wet Lab (Chemistry)}>, shape = "record", color = "#a50", fontcolor = "#a50", fillcolor = "#ffa", style = "filled"] - 110 [ label = <{{#3 | MISSING}|Equipment|Lapping Machine}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#ffddc1", style = "filled"] - 111 [ label = <{{#2 | MISSING}|Process|Chip Delayering}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 112 [ label = <{{#1 | MISSING}|Process|Chip Decapping}>, shape = "record", color = "#800", fontcolor = "#800", fillcolor = "#fcc", style = "filled"] - 0 -> 10 [ ] - 0 -> 15 [ ] - 0 -> 54 [ ] - 1 -> 21 [ ] - 1 -> 94 [ ] - 1 -> 100 [ ] - 2 -> 39 [ ] - 2 -> 49 [ ] - 3 -> 38 [ ] - 3 -> 43 [ ] - 3 -> 46 [ ] - 3 -> 67 [ ] - 3 -> 76 [ ] - 3 -> 82 [ ] - 4 -> 48 [ ] - 9 -> 6 [ ] - 9 -> 8 [ ] - 10 -> 11 [ ] - 10 -> 12 [ ] - 12 -> 16 [ ] - 14 -> 36 [ ] - 15 -> 62 [ ] - 16 -> 13 [ ] - 16 -> 14 [ ] - 16 -> 35 [ ] - 16 -> 56 [ ] - 16 -> 66 [ ] - 16 -> 109 [ ] - 21 -> 26 [ ] - 25 -> 17 [ ] - 25 -> 18 [ ] - 25 -> 19 [ ] - 25 -> 20 [ ] - 25 -> 22 [ ] - 25 -> 23 [ ] - 25 -> 24 [ ] - 26 -> 110 [ ] - 27 -> 7 [ ] - 28 -> 30 [ ] - 29 -> 51 [ ] - 32 -> 28 [ ] - 33 -> 28 [ ] - 33 -> 31 [ ] - 33 -> 32 [ ] - 34 -> 29 [ ] - 34 -> 33 [ ] - 35 -> 89 [ ] - 37 -> 104 [ ] - 38 -> 76 [ ] - 38 -> 83 [ ] - 38 -> 109 [ ] - 39 -> 42 [ ] - 42 -> 40 [ ] - 42 -> 41 [ ] - 43 -> 58 [ ] - 43 -> 72 [ ] - 44 -> 72 [ ] - 45 -> 66 [ ] - 45 -> 72 [ ] - 46 -> 68 [ ] - 48 -> 87 [ ] - 49 -> 5 [ ] - 49 -> 42 [ ] - 50 -> 109 [ ] - 52 -> 51 [ ] - 53 -> 45 [ ] - 53 -> 64 [ ] - 53 -> 65 [ ] - 54 -> 55 [ ] - 54 -> 57 [ ] - 54 -> 62 [ ] - 54 -> 98 [ ] - 55 -> 60 [ ] - 55 -> 66 [ ] - 56 -> 109 [ ] - 57 -> 44 [ ] - 57 -> 53 [ ] - 57 -> 56 [ ] - 57 -> 58 [ ] - 58 -> 59 [ ] - 58 -> 77 [ ] - 61 -> 60 [ ] - 62 -> 53 [ ] - 62 -> 61 [ ] - 62 -> 92 [ ] - 62 -> 108 [ ] - 64 -> 63 [ ] - 64 -> 66 [ ] - 65 -> 66 [ ] - 65 -> 75 [ ] - 65 -> 79 [ ] - 67 -> 68 [ ] - 68 -> 69 [ ] - 69 -> 70 [ ] - 70 -> 71 [ ] - 71 -> 72 [ ] - 72 -> 27 [ ] - 72 -> 34 [ ] - 72 -> 73 [ ] - 74 -> 85 [ ] - 75 -> 80 [ ] - 76 -> 58 [ ] - 76 -> 86 [ ] - 77 -> 36 [ ] - 78 -> 74 [ ] - 78 -> 112 [ ] - 79 -> 86 [ ] - 80 -> 86 [ ] - 81 -> 86 [ ] - 82 -> 81 [ ] - 82 -> 83 [ ] - 83 -> 84 [ ] - 84 -> 85 [ ] - 85 -> 86 [ ] - 86 -> 88 [ ] - 89 -> 87 [ ] - 89 -> 90 [ ] - 91 -> 87 [ ] - 92 -> 89 [ ] - 93 -> 112 [ ] - 94 -> 93 [ ] - 94 -> 95 [ ] - 94 -> 97 [ ] - 94 -> 98 [ ] - 95 -> 79 [ ] - 95 -> 96 [ ] - 96 -> 103 [ ] - 96 -> 111 [ ] - 97 -> 104 [ ] - 97 -> 112 [ ] - 98 -> 27 [ ] - 98 -> 104 [ ] - 100 -> 37 [ ] - 100 -> 101 [ ] - 100 -> 102 [ ] - 101 -> 109 [ ] - 102 -> 78 [ ] - 102 -> 83 [ ] - 102 -> 103 [ ] - 102 -> 111 [ ] - 103 -> 86 [ ] - 104 -> 52 [ ] - 104 -> 99 [ ] - 105 -> 89 [ ] - 107 -> 109 [ ] - 108 -> 109 [ ] - 109 -> 47 [ ] - 109 -> 48 [ ] - 109 -> 87 [ ] - 109 -> 91 [ ] - 111 -> 92 [ ] - 111 -> 106 [ ] - 111 -> 108 [ ] - 111 -> 110 [ ] - 111 -> 112 [ ] - 112 -> 105 [ ] - 112 -> 106 [ ] - 112 -> 107 [ ] - 112 -> 110 [ ] - -} diff --git a/techtree.svg b/techtree.svg deleted file mode 100644 index 195092c..0000000 --- a/techtree.svg +++ /dev/null @@ -1,2322 +0,0 @@ - - - - - - - - - -0 - -#115 - -MISSING - -Process - -Semiconductor Manufacturing - - - -10 - -#105 - -ASSIGNED - -Process - -Create a Backlit LCD Device - - - -0->10 - - - - - -15 - -#100 - -MISSING - -Process - -Etch fractals on wafers - - - -0->15 - - - - - -54 - -#61 - -MISSING - -Process - -Zeloof Z1 process - - - -0->54 - - - - - -1 - -#114 - -MISSING - -Process - -Semiconductor & Electronics Analysis - - - -21 - -#94 - -ASSIGNED - -Process - -PCB Reverse Engineering - - - -1->21 - - - - - -94 - -#19 - -MISSING - -Process - -IC Fault Injection - - - -1->94 - - - - - -100 - -#13 - -MISSING - -Process - -Full Chip Reverse Engineering - - - -1->100 - - - - - -2 - -#113 - -MISSING - -Process - -Science Communication - - - -39 - -#76 - -COMPLETED - -Process - -Video Streaming - - - -2->39 - - - - - -49 - -#66 - -COMPLETED - -Process - -Video Recording - - - -2->49 - - - - - -3 - -#112 - -MISSING - -Process - -Nanoscale Imaging - - - -38 - -#77 - -MISSING - -Process - -Imaging of Human Blood Cells - - - -3->38 - - - - - -43 - -#72 - -MISSING - -Process - -STM Non-metallic imaging - - - -3->43 - - - - - -46 - -#69 - -MISSING - -Research - -STM Surface Topography Manipulation - - - -3->46 - - - - - -67 - -#48 - -MISSING - -Process - -STM Imaging Individual Atoms - - - -3->67 - - - - - -76 - -#38 - -MISSING - -Process - -SEM Non-Metallic Imaging - - - -3->76 - - - - - -82 - -#32 - -MISSING - -Process - -SEM Imaging@10nm - - - -3->82 - - - - - -4 - -#111 - -MISSING - -Process - -LIN supply - - - -48 - -#67 - -ASSIGNED - -Equipment - -Chemical Storage/Containment - - - -4->48 - - - - - -9 - -#106 - -MISSING - -Process - -Electronics Distribution - - - -6 - -#109 - -MISSING - -Equipment - -Environmental Chamber - - - -9->6 - - - - - -8 - -#107 - -MISSING - -Process - -EMI Precompliance Testing - - - -9->8 - - - - - -25 - -#90 - -ASSIGNED - -Process - -PCB Repair & Rework - - - -17 - -#98 - -ASSIGNED - -Development - -Gridfinity-compatible JEDEC IC holders - - - -25->17 - - - - - -18 - -#97 - -COMPLETED - -Equipment - -PCB Rework Microscope - - - -25->18 - - - - - -19 - -#96 - -COMPLETED - -Equipment - -Soldering Station - - - -25->19 - - - - - -20 - -#95 - -MISSING - -Equipment - -Thermal Camera - - - -25->20 - - - - - -22 - -#93 - -COMPLETED - -Equipment - -HEPA Fume Extractor - - - -25->22 - - - - - -23 - -#92 - -COMPLETED - -Equipment - -Miscellaneous Tools for BGA Rework - - - -25->23 - - - - - -24 - -#91 - -COMPLETED - -Equipment - -Hot Air Rework Station - - - -25->24 - - - - - -50 - -#65 - -MISSING - -Process - -Gas chromatography - - - -109 - -#4 - -ASSIGNED - -Equipment - -Wet Lab (Chemistry) - - - -50->109 - - - - - -5 - -#110 - -ASSIGNED - -Equipment - -Local NAS/server - - - -7 - -#108 - -MISSING - -Development - -Piezo Driver - - - -11 - -#104 - -ASSIGNED - -Process - -Electroluminescence Foil - - - -10->11 - - - - - -12 - -#103 - -MISSING - -Process - -Create a Matrix LCD Device - - - -10->12 - - - - - -16 - -#99 - -ASSIGNED - -Process - -Create an LCD Device - - - -12->16 - - - - - -13 - -#102 - -ASSIGNED - -Research - -Acquire Liquid Crystal Fluid - - - -14 - -#101 - -MISSING - -Process - -Chemical Vapor Deposition - - - -36 - -#79 - -MISSING - -Equipment - -Sputter Coater - - - -14->36 - - - - - -62 - -#53 - -MISSING - -Process - -Pattern SiO₂ - - - -15->62 - - - - - -16->13 - - - - - -16->14 - - - - - -35 - -#80 - -MISSING - -Process - -Plasma Cleaning - - - -16->35 - - - - - -56 - -#59 - -MISSING - -Process - -H₃PO₄/HNO₃/AcOH Etching of Aluminum - - - -16->56 - - - - - -66 - -#49 - -ASSIGNED - -Development - -Spin Coater - - - -16->66 - - - - - -16->109 - - - - - -26 - -#89 - -ASSIGNED - -Process - -PCB Delayering - - - -21->26 - - - - - -110 - -#3 - -MISSING - -Equipment - -Lapping Machine - - - -26->110 - - - - - -27 - -#88 - -MISSING - -Development - -Piezo Slider Actuators - - - -27->7 - - - - - -28 - -#87 - -MISSING - -Equipment - -Diode Laser - - - -30 - -#85 - -ASSIGNED - -Development - -Laser Diode Driver/Controller - - - -28->30 - - - - - -29 - -#86 - -ASSIGNED - -Development - -Optomechanical System - - - -51 - -#64 - -COMPLETED - -Equipment - -FFF 3D-Printer - - - -29->51 - - - - - -31 - -#84 - -MISSING - -Equipment - -Stabilized Helium-Neon Laser - - - -32 - -#83 - -MISSING - -Equipment - -External Cavity Diode Laser (ECDL) - - - -32->28 - - - - - -33 - -#82 - -MISSING - -Equipment - -Coherent Light Source - - - -33->28 - - - - - -33->31 - - - - - -33->32 - - - - - -34 - -#81 - -MISSING - -Process - -Interferometry - - - -34->29 - - - - - -34->33 - - - - - -89 - -#25 - -MISSING - -Equipment - -Plasma Etcher - - - -35->89 - - - - - -37 - -#78 - -ASSIGNED - -Process - -Optical Die Imaging - - - -104 - -#9 - -ASSIGNED - -Development - -Optical Microscope: Motorized Stage - - - -37->104 - - - - - -38->76 - - - - - -83 - -#31 - -COMPLETED - -Process - -SEM Imaging@100nm - - - -38->83 - - - - - -38->109 - - - - - -42 - -#73 - -COMPLETED - -Development - -Main Camera Rig - - - -39->42 - - - - - -40 - -#75 - -COMPLETED - -Equipment - -Microphone(s) - - - -41 - -#74 - -COMPLETED - -Equipment - -Camcorder - - - -42->40 - - - - - -42->41 - - - - - -58 - -#57 - -MISSING - -Process - -Metal thin film deposition - - - -43->58 - - - - - -72 - -#43 - -ASSIGNED - -Development - -Scanning Tunneling Microscope (STM) - - - -43->72 - - - - - -44 - -#71 - -MISSING - -Research - -STM Metal Pattern Deposition - - - -44->72 - - - - - -45 - -#70 - -MISSING - -Research - -STM Lithography - - - -45->66 - - - - - -45->72 - - - - - -68 - -#47 - -MISSING - -Process - -STM Imaging@1nm - - - -46->68 - - - - - -47 - -#68 - -ASSIGNED - -Equipment - -Heating Stirrer - - - -87 - -#27 - -ASSIGNED - -Equipment - -Technical Ventilation - - - -48->87 - - - - - -49->5 - - - - - -49->42 - - - - - -52 - -#63 - -ASSIGNED - -Equipment - -OpenFlexure Delta Stage - - - -52->51 - - - - - -53 - -#62 - -MISSING - -Process - -Lithography - - - -53->45 - - - - - -64 - -#51 - -MISSING - -Process - -Maskless Photolithography - - - -53->64 - - - - - -65 - -#50 - -MISSING - -Process - -E-beam Lithography - - - -53->65 - - - - - -55 - -#60 - -MISSING - -Process - -Thermal diffusion doping - - - -54->55 - - - - - -57 - -#58 - -MISSING - -Process - -Pattern Al - - - -54->57 - - - - - -54->62 - - - - - -98 - -#15 - -MISSING - -Process - -Microprobing - - - -54->98 - - - - - -60 - -#55 - -ASSIGNED - -Equipment - -Tube Furnace - - - -55->60 - - - - - -55->66 - - - - - -56->109 - - - - - -57->44 - - - - - -57->53 - - - - - -57->56 - - - - - -57->58 - - - - - -59 - -#56 - -MISSING - -Process - -PVD: Thermal Evaporation - - - -58->59 - - - - - -77 - -#37 - -MISSING - -Process - -PVD: Sputtering - - - -58->77 - - - - - -61 - -#54 - -MISSING - -Process - -Si Thermal Oxidation - - - -61->60 - - - - - -62->53 - - - - - -62->61 - - - - - -92 - -#21 - -MISSING - -Process - -Reactive Ion Etching - - - -62->92 - - - - - -108 - -#5 - -MISSING - -Process - -BOE Etching of SiO₂ - - - -62->108 - - - - - -63 - -#52 - -MISSING - -Development - -Maskless photolithography stepper - - - -64->63 - - - - - -64->66 - - - - - -65->66 - - - - - -75 - -#39 - -ASSIGNED - -Development - -OBI Lite - - - -65->75 - - - - - -79 - -#35 - -MISSING - -Equipment - -SEM Beam Blanker - - - -65->79 - - - - - -67->68 - - - - - -69 - -#46 - -MISSING - -Process - -STM Imaging@10nm - - - -68->69 - - - - - -70 - -#45 - -ASSIGNED - -Process - -STM Imaging@100nm - - - -69->70 - - - - - -71 - -#44 - -ASSIGNED - -Process - -STM Imaging@1µm - - - -70->71 - - - - - -71->72 - - - - - -72->27 - - - - - -72->34 - - - - - -73 - -#42 - -COMPLETED - -Development - -STM Vibration Isolation - - - -72->73 - - - - - -74 - -#41 - -COMPLETED - -Process - -Primitive Die Imaging - - - -85 - -#29 - -COMPLETED - -Process - -SEM Imaging@10µm - - - -74->85 - - - - - -80 - -#34 - -COMPLETED - -Equipment - -Open Beam Interface - - - -75->80 - - - - - -76->58 - - - - - -86 - -#28 - -COMPLETED - -Equipment - -Scanning Electron Microscope (SEM) - - - -76->86 - - - - - -77->36 - - - - - -78 - -#36 - -MISSING - -Process - -Primitive Chip Imaging - - - -78->74 - - - - - -112 - -#1 - -MISSING - -Process - -Chip Decapping - - - -78->112 - - - - - -79->86 - - - - - -80->86 - - - - - -81 - -#33 - -COMPLETED - -Equipment - -SEM Vibration Isolation - - - -81->86 - - - - - -82->81 - - - - - -82->83 - - - - - -84 - -#30 - -COMPLETED - -Process - -SEM Imaging@1µm - - - -83->84 - - - - - -84->85 - - - - - -85->86 - - - - - -88 - -#26 - -COMPLETED - -Equipment - -Water Cooling - - - -86->88 - - - - - -89->87 - - - - - -90 - -#24 - -COMPLETED - -Equipment - -~3ph Power (below 10kW) - - - -89->90 - - - - - -91 - -#23 - -ASSIGNED - -Equipment - -Fume Hood - - - -91->87 - - - - - -92->89 - - - - - -93 - -#20 - -MISSING - -Process - -Body Bias Injection - - - -93->112 - - - - - -94->93 - - - - - -95 - -#18 - -MISSING - -Research - -SEM Fault Injection - - - -94->95 - - - - - -97 - -#16 - -MISSING - -Process - -Laser Fault Injection - - - -94->97 - - - - - -94->98 - - - - - -95->79 - - - - - -96 - -#17 - -MISSING - -Process - -SEM Nanoprobing/Live Analysis - - - -95->96 - - - - - -103 - -#10 - -ASSIGNED - -Equipment - -SEM: Motorized Stage - - - -96->103 - - - - - -111 - -#2 - -MISSING - -Process - -Chip Delayering - - - -96->111 - - - - - -97->104 - - - - - -97->112 - - - - - -98->27 - - - - - -98->104 - - - - - -99 - -#14 - -ASSIGNED - -Equipment - -Optical Microscope - - - -100->37 - - - - - -101 - -#12 - -MISSING - -Process - -DASH Stain - - - -100->101 - - - - - -102 - -#11 - -MISSING - -Process - -Simple Chip Imaging - - - -100->102 - - - - - -101->109 - - - - - -102->78 - - - - - -102->83 - - - - - -102->103 - - - - - -102->111 - - - - - -103->86 - - - - - -104->52 - - - - - -104->99 - - - - - -105 - -#8 - -MISSING - -Process - -Plasma Etching - - - -105->89 - - - - - -106 - -#7 - -MISSING - -Process - -Laser Cutting - - - -107 - -#6 - -MISSING - -Process - -HNO₃/H₂SO₄ Decapping - - - -107->109 - - - - - -108->109 - - - - - -109->47 - - - - - -109->48 - - - - - -109->87 - - - - - -109->91 - - - - - -111->92 - - - - - -111->106 - - - - - -111->108 - - - - - -111->110 - - - - - -111->112 - - - - - -112->105 - - - - - -112->106 - - - - - -112->107 - - - - - -112->110 - - - - -